diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md
new file mode 100644
index 0000000..19da528
--- /dev/null
+++ b/.github/ISSUE_TEMPLATE/bug_report.md
@@ -0,0 +1,31 @@
+---
+name: Bug report
+about: Create a report to help us improve
+title: ''
+labels: bug
+assignees: ''
+
+---
+
+**Describe the bug**
+
+
+**Expected behavior**
+
+
+**How to reproduce**
+
+
+**Screenshots**
+
+
+**Software version**
+
+
+**Desktop (please complete the following information if relevant):**
+ - OS:
+ - Browser:
+ - Version:
+
+**Additional context**
+
diff --git a/.github/ISSUE_TEMPLATE/config.yml b/.github/ISSUE_TEMPLATE/config.yml
new file mode 100644
index 0000000..3363fc3
--- /dev/null
+++ b/.github/ISSUE_TEMPLATE/config.yml
@@ -0,0 +1,11 @@
+blank_issues_enabled: false
+contact_links:
+ - name: Doc issue report
+ url: https://github.com/teragrep/doc_01/issues/new?template=doc-issue-report.md
+ about: Problems with Teragrep documentation
+ - name: Ask a question or get support
+ url: https://github.com/teragrep/cfe_16/discussions
+ about: Ask a question or request support
+ - name: Report vulnerability
+ url: https://github.com/teragrep/teragrep/security/advisories/new
+ about: Privately report a security vulnerability
diff --git a/.github/ISSUE_TEMPLATE/feature_request.md b/.github/ISSUE_TEMPLATE/feature_request.md
new file mode 100644
index 0000000..9baeccc
--- /dev/null
+++ b/.github/ISSUE_TEMPLATE/feature_request.md
@@ -0,0 +1,20 @@
+---
+name: Feature request
+about: Suggest an idea for this project
+title: ''
+labels: enhancement
+assignees: ''
+
+---
+
+**Description**
+
+
+**Use case or motivation behind the feature request**
+
+
+**Related issues**
+
+
+**Additional context**
+
diff --git a/.github/ISSUE_TEMPLATE/tasks-and-meta.md b/.github/ISSUE_TEMPLATE/tasks-and-meta.md
new file mode 100644
index 0000000..17546e4
--- /dev/null
+++ b/.github/ISSUE_TEMPLATE/tasks-and-meta.md
@@ -0,0 +1,11 @@
+---
+name: Tasks and meta
+about: Maintainers only
+title: ''
+labels: ''
+assignees: ''
+
+---
+
+**Description**
+
diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml
new file mode 100644
index 0000000..8036b86
--- /dev/null
+++ b/.github/workflows/ci.yaml
@@ -0,0 +1,51 @@
+name: CI
+
+on: push
+
+jobs:
+ verify:
+ name: Verify Code
+ runs-on: ubuntu-latest
+
+ env:
+ COVERITY: coverity_tool
+
+ steps:
+ - uses: actions/checkout@v3
+
+ - name: Setup Maven Central
+ uses: actions/setup-java@v3
+ with:
+ java-version: 8.0.292+10
+ distribution: 'adopt'
+
+ - name: Cache Local Maven Repository
+ uses: actions/cache@v3
+ with:
+ path: ~/.m2/repository
+ key: ${{ runner.os }}-maven-${{ hashFiles('**/pom.xml') }}
+
+ - name: Compile Test and Verify
+ run: mvn --batch-mode clean verify
+
+ - name: Cache Coverity
+ id: cache_coverity
+ uses: actions/cache@v3
+ with:
+ path: ${{ env.COVERITY }}
+ key: coverity
+
+ - name: Download Coverity
+ if: steps.cache_coverity.outputs.cache-hit != 'true'
+ run: |
+ wget --quiet https://scan.coverity.com/download/linux64 --post-data "token=${{ secrets.COVERITY_TOKEN }}&project=cfe_16" -O ${{ env.COVERITY }}.tgz
+ mkdir -p ${{ env.COVERITY }}
+ tar zxvf ${{ env.COVERITY }}.tgz -C ${{ env.COVERITY }} --strip-components 1
+
+ - name: Compile Coverity
+ run: |
+ ${{ env.COVERITY }}/bin/cov-build --dir cov-int mvn -DskipTests=true --batch-mode clean compile
+ tar czvf cfe_16.tgz cov-int
+
+ - name: Upload to Coverity
+ run: curl --silent --form token=${{ secrets.COVERITY_TOKEN }} --form email=${{ secrets.COVERITY_EMAIL }} --form file=@cfe_16.tgz --form version="${GITHUB_REF##*/}" --form description="automated upload" https://scan.coverity.com/builds?project=cfe_16
diff --git a/.github/workflows/upload_release.yaml b/.github/workflows/upload_release.yaml
new file mode 100644
index 0000000..2d1b219
--- /dev/null
+++ b/.github/workflows/upload_release.yaml
@@ -0,0 +1,65 @@
+name: Upload Release
+
+on:
+ release:
+ types: published
+
+jobs:
+ upload:
+ name: Upload
+ runs-on: ubuntu-latest
+ permissions:
+ contents: write
+ packages: write
+
+ steps:
+ - uses: actions/checkout@v3
+ with:
+ fetch-depth: 0
+
+ - name: Cache Local Maven Repository
+ uses: actions/cache@v3
+ with:
+ path: ~/.m2/repository
+ key: ${{ runner.os }}-maven-${{ hashFiles('**/pom.xml') }}
+
+ - name: Setup GitHub Packages
+ uses: actions/setup-java@v3
+ with:
+ java-version: 8.0.292+10
+ distribution: 'adopt'
+
+ - name: Get version
+ run: git describe --tags && echo "RELEASE_VERSION=$(git describe --tags)" >> $GITHUB_ENV
+
+ - name: Create jar and rpm
+ run: |
+ mvn -B -Drevision=${{ env.RELEASE_VERSION }} -Dsha1= -Dchangelist= clean verify deploy;
+ cd rpm/ && mvn -B -Drevision=${{ env.RELEASE_VERSION }} -Dsha1= -Dchangelist= -f rpm.pom.xml package;
+ env:
+ GITHUB_TOKEN: ${{ github.token }}
+
+ - name: Attach rpm as artifact on releases
+ uses: softprops/action-gh-release@v1
+ if: startsWith(github.ref, 'refs/tags/')
+ with:
+ files: target/rpm/com.teragrep-cfe_16/RPMS/noarch/com.teragrep-cfe_16-*.rpm
+
+ - name: Set up Docker Buildx
+ uses: docker/setup-buildx-action@v2.5.0
+
+ - name: Login to GitHub Container Registry
+ uses: docker/login-action@v2.1.0
+ with:
+ registry: ghcr.io
+ username: ${{ github.actor }}
+ password: ${{ secrets.GITHUB_TOKEN }}
+
+ - name: Lowercase repository name
+ run: echo "REPO_LC=${GITHUB_REPOSITORY,,}" >> $GITHUB_ENV
+
+ - name: 'Build Image'
+ run: |
+ docker buildx build --output type=docker --tag ghcr.io/${{ env.REPO_LC }}/app:${{ github.event.release.tag_name }} .
+ docker push ghcr.io/${{ env.REPO_LC }}/app:${{ github.event.release.tag_name }}
+
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..3e7ba63
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,3 @@
+.idea/
+target/
+rpm/target/
diff --git a/Dockerfile b/Dockerfile
new file mode 100644
index 0000000..da35738
--- /dev/null
+++ b/Dockerfile
@@ -0,0 +1,8 @@
+FROM rockylinux:9
+COPY rpm/target/rpm/com.teragrep-cfe_16/RPMS/noarch/com.teragrep-cfe_16-*.rpm /rpm/
+RUN yum -y localinstall /rpm/*.rpm && yum clean all
+
+COPY entrypoint.sh /entrypoint.sh
+RUN chmod +x /entrypoint.sh
+USER srv-cfe_16
+ENTRYPOINT /entrypoint.sh
diff --git a/LICENSE b/LICENSE
new file mode 100644
index 0000000..05587a3
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,687 @@
+ GNU AFFERO GENERAL PUBLIC LICENSE
+ Version 3, 19 November 2007
+
+ Copyright (C) 2007 Free Software Foundation, Inc.
+ Everyone is permitted to copy and distribute verbatim copies
+ of this license document, but changing it is not allowed.
+
+ Preamble
+
+ The GNU Affero General Public License is a free, copyleft license for
+software and other kinds of works, specifically designed to ensure
+cooperation with the community in the case of network server software.
+
+ The licenses for most software and other practical works are designed
+to take away your freedom to share and change the works. By contrast,
+our General Public Licenses are intended to guarantee your freedom to
+share and change all versions of a program--to make sure it remains free
+software for all its users.
+
+ When we speak of free software, we are referring to freedom, not
+price. Our General Public Licenses are designed to make sure that you
+have the freedom to distribute copies of free software (and charge for
+them if you wish), that you receive source code or can get it if you
+want it, that you can change the software or use pieces of it in new
+free programs, and that you know you can do these things.
+
+ Developers that use our General Public Licenses protect your rights
+with two steps: (1) assert copyright on the software, and (2) offer
+you this License which gives you legal permission to copy, distribute
+and/or modify the software.
+
+ A secondary benefit of defending all users' freedom is that
+improvements made in alternate versions of the program, if they
+receive widespread use, become available for other developers to
+incorporate. Many developers of free software are heartened and
+encouraged by the resulting cooperation. However, in the case of
+software used on network servers, this result may fail to come about.
+The GNU General Public License permits making a modified version and
+letting the public access it on a server without ever releasing its
+source code to the public.
+
+ The GNU Affero General Public License is designed specifically to
+ensure that, in such cases, the modified source code becomes available
+to the community. It requires the operator of a network server to
+provide the source code of the modified version running there to the
+users of that server. Therefore, public use of a modified version, on
+a publicly accessible server, gives the public access to the source
+code of the modified version.
+
+ An older license, called the Affero General Public License and
+published by Affero, was designed to accomplish similar goals. This is
+a different license, not a version of the Affero GPL, but Affero has
+released a new version of the Affero GPL which permits relicensing under
+this license.
+
+ The precise terms and conditions for copying, distribution and
+modification follow.
+
+ TERMS AND CONDITIONS
+
+ 0. Definitions.
+
+ "This License" refers to version 3 of the GNU Affero General Public License.
+
+ "Copyright" also means copyright-like laws that apply to other kinds of
+works, such as semiconductor masks.
+
+ "The Program" refers to any copyrightable work licensed under this
+License. Each licensee is addressed as "you". "Licensees" and
+"recipients" may be individuals or organizations.
+
+ To "modify" a work means to copy from or adapt all or part of the work
+in a fashion requiring copyright permission, other than the making of an
+exact copy. The resulting work is called a "modified version" of the
+earlier work or a work "based on" the earlier work.
+
+ A "covered work" means either the unmodified Program or a work based
+on the Program.
+
+ To "propagate" a work means to do anything with it that, without
+permission, would make you directly or secondarily liable for
+infringement under applicable copyright law, except executing it on a
+computer or modifying a private copy. Propagation includes copying,
+distribution (with or without modification), making available to the
+public, and in some countries other activities as well.
+
+ To "convey" a work means any kind of propagation that enables other
+parties to make or receive copies. Mere interaction with a user through
+a computer network, with no transfer of a copy, is not conveying.
+
+ An interactive user interface displays "Appropriate Legal Notices"
+to the extent that it includes a convenient and prominently visible
+feature that (1) displays an appropriate copyright notice, and (2)
+tells the user that there is no warranty for the work (except to the
+extent that warranties are provided), that licensees may convey the
+work under this License, and how to view a copy of this License. If
+the interface presents a list of user commands or options, such as a
+menu, a prominent item in the list meets this criterion.
+
+ 1. Source Code.
+
+ The "source code" for a work means the preferred form of the work
+for making modifications to it. "Object code" means any non-source
+form of a work.
+
+ A "Standard Interface" means an interface that either is an official
+standard defined by a recognized standards body, or, in the case of
+interfaces specified for a particular programming language, one that
+is widely used among developers working in that language.
+
+ The "System Libraries" of an executable work include anything, other
+than the work as a whole, that (a) is included in the normal form of
+packaging a Major Component, but which is not part of that Major
+Component, and (b) serves only to enable use of the work with that
+Major Component, or to implement a Standard Interface for which an
+implementation is available to the public in source code form. A
+"Major Component", in this context, means a major essential component
+(kernel, window system, and so on) of the specific operating system
+(if any) on which the executable work runs, or a compiler used to
+produce the work, or an object code interpreter used to run it.
+
+ The "Corresponding Source" for a work in object code form means all
+the source code needed to generate, install, and (for an executable
+work) run the object code and to modify the work, including scripts to
+control those activities. However, it does not include the work's
+System Libraries, or general-purpose tools or generally available free
+programs which are used unmodified in performing those activities but
+which are not part of the work. For example, Corresponding Source
+includes interface definition files associated with source files for
+the work, and the source code for shared libraries and dynamically
+linked subprograms that the work is specifically designed to require,
+such as by intimate data communication or control flow between those
+subprograms and other parts of the work.
+
+ The Corresponding Source need not include anything that users
+can regenerate automatically from other parts of the Corresponding
+Source.
+
+ The Corresponding Source for a work in source code form is that
+same work.
+
+ 2. Basic Permissions.
+
+ All rights granted under this License are granted for the term of
+copyright on the Program, and are irrevocable provided the stated
+conditions are met. This License explicitly affirms your unlimited
+permission to run the unmodified Program. The output from running a
+covered work is covered by this License only if the output, given its
+content, constitutes a covered work. This License acknowledges your
+rights of fair use or other equivalent, as provided by copyright law.
+
+ You may make, run and propagate covered works that you do not
+convey, without conditions so long as your license otherwise remains
+in force. You may convey covered works to others for the sole purpose
+of having them make modifications exclusively for you, or provide you
+with facilities for running those works, provided that you comply with
+the terms of this License in conveying all material for which you do
+not control copyright. Those thus making or running the covered works
+for you must do so exclusively on your behalf, under your direction
+and control, on terms that prohibit them from making any copies of
+your copyrighted material outside their relationship with you.
+
+ Conveying under any other circumstances is permitted solely under
+the conditions stated below. Sublicensing is not allowed; section 10
+makes it unnecessary.
+
+ 3. Protecting Users' Legal Rights From Anti-Circumvention Law.
+
+ No covered work shall be deemed part of an effective technological
+measure under any applicable law fulfilling obligations under article
+11 of the WIPO copyright treaty adopted on 20 December 1996, or
+similar laws prohibiting or restricting circumvention of such
+measures.
+
+ When you convey a covered work, you waive any legal power to forbid
+circumvention of technological measures to the extent such circumvention
+is effected by exercising rights under this License with respect to
+the covered work, and you disclaim any intention to limit operation or
+modification of the work as a means of enforcing, against the work's
+users, your or third parties' legal rights to forbid circumvention of
+technological measures.
+
+ 4. Conveying Verbatim Copies.
+
+ You may convey verbatim copies of the Program's source code as you
+receive it, in any medium, provided that you conspicuously and
+appropriately publish on each copy an appropriate copyright notice;
+keep intact all notices stating that this License and any
+non-permissive terms added in accord with section 7 apply to the code;
+keep intact all notices of the absence of any warranty; and give all
+recipients a copy of this License along with the Program.
+
+ You may charge any price or no price for each copy that you convey,
+and you may offer support or warranty protection for a fee.
+
+ 5. Conveying Modified Source Versions.
+
+ You may convey a work based on the Program, or the modifications to
+produce it from the Program, in the form of source code under the
+terms of section 4, provided that you also meet all of these conditions:
+
+ a) The work must carry prominent notices stating that you modified
+ it, and giving a relevant date.
+
+ b) The work must carry prominent notices stating that it is
+ released under this License and any conditions added under section
+ 7. This requirement modifies the requirement in section 4 to
+ "keep intact all notices".
+
+ c) You must license the entire work, as a whole, under this
+ License to anyone who comes into possession of a copy. This
+ License will therefore apply, along with any applicable section 7
+ additional terms, to the whole of the work, and all its parts,
+ regardless of how they are packaged. This License gives no
+ permission to license the work in any other way, but it does not
+ invalidate such permission if you have separately received it.
+
+ d) If the work has interactive user interfaces, each must display
+ Appropriate Legal Notices; however, if the Program has interactive
+ interfaces that do not display Appropriate Legal Notices, your
+ work need not make them do so.
+
+ A compilation of a covered work with other separate and independent
+works, which are not by their nature extensions of the covered work,
+and which are not combined with it such as to form a larger program,
+in or on a volume of a storage or distribution medium, is called an
+"aggregate" if the compilation and its resulting copyright are not
+used to limit the access or legal rights of the compilation's users
+beyond what the individual works permit. Inclusion of a covered work
+in an aggregate does not cause this License to apply to the other
+parts of the aggregate.
+
+ 6. Conveying Non-Source Forms.
+
+ You may convey a covered work in object code form under the terms
+of sections 4 and 5, provided that you also convey the
+machine-readable Corresponding Source under the terms of this License,
+in one of these ways:
+
+ a) Convey the object code in, or embodied in, a physical product
+ (including a physical distribution medium), accompanied by the
+ Corresponding Source fixed on a durable physical medium
+ customarily used for software interchange.
+
+ b) Convey the object code in, or embodied in, a physical product
+ (including a physical distribution medium), accompanied by a
+ written offer, valid for at least three years and valid for as
+ long as you offer spare parts or customer support for that product
+ model, to give anyone who possesses the object code either (1) a
+ copy of the Corresponding Source for all the software in the
+ product that is covered by this License, on a durable physical
+ medium customarily used for software interchange, for a price no
+ more than your reasonable cost of physically performing this
+ conveying of source, or (2) access to copy the
+ Corresponding Source from a network server at no charge.
+
+ c) Convey individual copies of the object code with a copy of the
+ written offer to provide the Corresponding Source. This
+ alternative is allowed only occasionally and noncommercially, and
+ only if you received the object code with such an offer, in accord
+ with subsection 6b.
+
+ d) Convey the object code by offering access from a designated
+ place (gratis or for a charge), and offer equivalent access to the
+ Corresponding Source in the same way through the same place at no
+ further charge. You need not require recipients to copy the
+ Corresponding Source along with the object code. If the place to
+ copy the object code is a network server, the Corresponding Source
+ may be on a different server (operated by you or a third party)
+ that supports equivalent copying facilities, provided you maintain
+ clear directions next to the object code saying where to find the
+ Corresponding Source. Regardless of what server hosts the
+ Corresponding Source, you remain obligated to ensure that it is
+ available for as long as needed to satisfy these requirements.
+
+ e) Convey the object code using peer-to-peer transmission, provided
+ you inform other peers where the object code and Corresponding
+ Source of the work are being offered to the general public at no
+ charge under subsection 6d.
+
+ A separable portion of the object code, whose source code is excluded
+from the Corresponding Source as a System Library, need not be
+included in conveying the object code work.
+
+ A "User Product" is either (1) a "consumer product", which means any
+tangible personal property which is normally used for personal, family,
+or household purposes, or (2) anything designed or sold for incorporation
+into a dwelling. In determining whether a product is a consumer product,
+doubtful cases shall be resolved in favor of coverage. For a particular
+product received by a particular user, "normally used" refers to a
+typical or common use of that class of product, regardless of the status
+of the particular user or of the way in which the particular user
+actually uses, or expects or is expected to use, the product. A product
+is a consumer product regardless of whether the product has substantial
+commercial, industrial or non-consumer uses, unless such uses represent
+the only significant mode of use of the product.
+
+ "Installation Information" for a User Product means any methods,
+procedures, authorization keys, or other information required to install
+and execute modified versions of a covered work in that User Product from
+a modified version of its Corresponding Source. The information must
+suffice to ensure that the continued functioning of the modified object
+code is in no case prevented or interfered with solely because
+modification has been made.
+
+ If you convey an object code work under this section in, or with, or
+specifically for use in, a User Product, and the conveying occurs as
+part of a transaction in which the right of possession and use of the
+User Product is transferred to the recipient in perpetuity or for a
+fixed term (regardless of how the transaction is characterized), the
+Corresponding Source conveyed under this section must be accompanied
+by the Installation Information. But this requirement does not apply
+if neither you nor any third party retains the ability to install
+modified object code on the User Product (for example, the work has
+been installed in ROM).
+
+ The requirement to provide Installation Information does not include a
+requirement to continue to provide support service, warranty, or updates
+for a work that has been modified or installed by the recipient, or for
+the User Product in which it has been modified or installed. Access to a
+network may be denied when the modification itself materially and
+adversely affects the operation of the network or violates the rules and
+protocols for communication across the network.
+
+ Corresponding Source conveyed, and Installation Information provided,
+in accord with this section must be in a format that is publicly
+documented (and with an implementation available to the public in
+source code form), and must require no special password or key for
+unpacking, reading or copying.
+
+ 7. Additional Terms.
+
+ "Additional permissions" are terms that supplement the terms of this
+License by making exceptions from one or more of its conditions.
+Additional permissions that are applicable to the entire Program shall
+be treated as though they were included in this License, to the extent
+that they are valid under applicable law. If additional permissions
+apply only to part of the Program, that part may be used separately
+under those permissions, but the entire Program remains governed by
+this License without regard to the additional permissions.
+
+ When you convey a copy of a covered work, you may at your option
+remove any additional permissions from that copy, or from any part of
+it. (Additional permissions may be written to require their own
+removal in certain cases when you modify the work.) You may place
+additional permissions on material, added by you to a covered work,
+for which you have or can give appropriate copyright permission.
+
+ Notwithstanding any other provision of this License, for material you
+add to a covered work, you may (if authorized by the copyright holders of
+that material) supplement the terms of this License with terms:
+
+ a) Disclaiming warranty or limiting liability differently from the
+ terms of sections 15 and 16 of this License; or
+
+ b) Requiring preservation of specified reasonable legal notices or
+ author attributions in that material or in the Appropriate Legal
+ Notices displayed by works containing it; or
+
+ c) Prohibiting misrepresentation of the origin of that material, or
+ requiring that modified versions of such material be marked in
+ reasonable ways as different from the original version; or
+
+ d) Limiting the use for publicity purposes of names of licensors or
+ authors of the material; or
+
+ e) Declining to grant rights under trademark law for use of some
+ trade names, trademarks, or service marks; or
+
+ f) Requiring indemnification of licensors and authors of that
+ material by anyone who conveys the material (or modified versions of
+ it) with contractual assumptions of liability to the recipient, for
+ any liability that these contractual assumptions directly impose on
+ those licensors and authors.
+
+ All other non-permissive additional terms are considered "further
+restrictions" within the meaning of section 10. If the Program as you
+received it, or any part of it, contains a notice stating that it is
+governed by this License along with a term that is a further
+restriction, you may remove that term. If a license document contains
+a further restriction but permits relicensing or conveying under this
+License, you may add to a covered work material governed by the terms
+of that license document, provided that the further restriction does
+not survive such relicensing or conveying.
+
+ If you add terms to a covered work in accord with this section, you
+must place, in the relevant source files, a statement of the
+additional terms that apply to those files, or a notice indicating
+where to find the applicable terms.
+
+ Additional terms, permissive or non-permissive, may be stated in the
+form of a separately written license, or stated as exceptions;
+the above requirements apply either way.
+
+ 8. Termination.
+
+ You may not propagate or modify a covered work except as expressly
+provided under this License. Any attempt otherwise to propagate or
+modify it is void, and will automatically terminate your rights under
+this License (including any patent licenses granted under the third
+paragraph of section 11).
+
+ However, if you cease all violation of this License, then your
+license from a particular copyright holder is reinstated (a)
+provisionally, unless and until the copyright holder explicitly and
+finally terminates your license, and (b) permanently, if the copyright
+holder fails to notify you of the violation by some reasonable means
+prior to 60 days after the cessation.
+
+ Moreover, your license from a particular copyright holder is
+reinstated permanently if the copyright holder notifies you of the
+violation by some reasonable means, this is the first time you have
+received notice of violation of this License (for any work) from that
+copyright holder, and you cure the violation prior to 30 days after
+your receipt of the notice.
+
+ Termination of your rights under this section does not terminate the
+licenses of parties who have received copies or rights from you under
+this License. If your rights have been terminated and not permanently
+reinstated, you do not qualify to receive new licenses for the same
+material under section 10.
+
+ 9. Acceptance Not Required for Having Copies.
+
+ You are not required to accept this License in order to receive or
+run a copy of the Program. Ancillary propagation of a covered work
+occurring solely as a consequence of using peer-to-peer transmission
+to receive a copy likewise does not require acceptance. However,
+nothing other than this License grants you permission to propagate or
+modify any covered work. These actions infringe copyright if you do
+not accept this License. Therefore, by modifying or propagating a
+covered work, you indicate your acceptance of this License to do so.
+
+ 10. Automatic Licensing of Downstream Recipients.
+
+ Each time you convey a covered work, the recipient automatically
+receives a license from the original licensors, to run, modify and
+propagate that work, subject to this License. You are not responsible
+for enforcing compliance by third parties with this License.
+
+ An "entity transaction" is a transaction transferring control of an
+organization, or substantially all assets of one, or subdividing an
+organization, or merging organizations. If propagation of a covered
+work results from an entity transaction, each party to that
+transaction who receives a copy of the work also receives whatever
+licenses to the work the party's predecessor in interest had or could
+give under the previous paragraph, plus a right to possession of the
+Corresponding Source of the work from the predecessor in interest, if
+the predecessor has it or can get it with reasonable efforts.
+
+ You may not impose any further restrictions on the exercise of the
+rights granted or affirmed under this License. For example, you may
+not impose a license fee, royalty, or other charge for exercise of
+rights granted under this License, and you may not initiate litigation
+(including a cross-claim or counterclaim in a lawsuit) alleging that
+any patent claim is infringed by making, using, selling, offering for
+sale, or importing the Program or any portion of it.
+
+ 11. Patents.
+
+ A "contributor" is a copyright holder who authorizes use under this
+License of the Program or a work on which the Program is based. The
+work thus licensed is called the contributor's "contributor version".
+
+ A contributor's "essential patent claims" are all patent claims
+owned or controlled by the contributor, whether already acquired or
+hereafter acquired, that would be infringed by some manner, permitted
+by this License, of making, using, or selling its contributor version,
+but do not include claims that would be infringed only as a
+consequence of further modification of the contributor version. For
+purposes of this definition, "control" includes the right to grant
+patent sublicenses in a manner consistent with the requirements of
+this License.
+
+ Each contributor grants you a non-exclusive, worldwide, royalty-free
+patent license under the contributor's essential patent claims, to
+make, use, sell, offer for sale, import and otherwise run, modify and
+propagate the contents of its contributor version.
+
+ In the following three paragraphs, a "patent license" is any express
+agreement or commitment, however denominated, not to enforce a patent
+(such as an express permission to practice a patent or covenant not to
+sue for patent infringement). To "grant" such a patent license to a
+party means to make such an agreement or commitment not to enforce a
+patent against the party.
+
+ If you convey a covered work, knowingly relying on a patent license,
+and the Corresponding Source of the work is not available for anyone
+to copy, free of charge and under the terms of this License, through a
+publicly available network server or other readily accessible means,
+then you must either (1) cause the Corresponding Source to be so
+available, or (2) arrange to deprive yourself of the benefit of the
+patent license for this particular work, or (3) arrange, in a manner
+consistent with the requirements of this License, to extend the patent
+license to downstream recipients. "Knowingly relying" means you have
+actual knowledge that, but for the patent license, your conveying the
+covered work in a country, or your recipient's use of the covered work
+in a country, would infringe one or more identifiable patents in that
+country that you have reason to believe are valid.
+
+ If, pursuant to or in connection with a single transaction or
+arrangement, you convey, or propagate by procuring conveyance of, a
+covered work, and grant a patent license to some of the parties
+receiving the covered work authorizing them to use, propagate, modify
+or convey a specific copy of the covered work, then the patent license
+you grant is automatically extended to all recipients of the covered
+work and works based on it.
+
+ A patent license is "discriminatory" if it does not include within
+the scope of its coverage, prohibits the exercise of, or is
+conditioned on the non-exercise of one or more of the rights that are
+specifically granted under this License. You may not convey a covered
+work if you are a party to an arrangement with a third party that is
+in the business of distributing software, under which you make payment
+to the third party based on the extent of your activity of conveying
+the work, and under which the third party grants, to any of the
+parties who would receive the covered work from you, a discriminatory
+patent license (a) in connection with copies of the covered work
+conveyed by you (or copies made from those copies), or (b) primarily
+for and in connection with specific products or compilations that
+contain the covered work, unless you entered into that arrangement,
+or that patent license was granted, prior to 28 March 2007.
+
+ Nothing in this License shall be construed as excluding or limiting
+any implied license or other defenses to infringement that may
+otherwise be available to you under applicable patent law.
+
+ 12. No Surrender of Others' Freedom.
+
+ If conditions are imposed on you (whether by court order, agreement or
+otherwise) that contradict the conditions of this License, they do not
+excuse you from the conditions of this License. If you cannot convey a
+covered work so as to satisfy simultaneously your obligations under this
+License and any other pertinent obligations, then as a consequence you may
+not convey it at all. For example, if you agree to terms that obligate you
+to collect a royalty for further conveying from those to whom you convey
+the Program, the only way you could satisfy both those terms and this
+License would be to refrain entirely from conveying the Program.
+
+ 13. Remote Network Interaction; Use with the GNU General Public License.
+
+ Notwithstanding any other provision of this License, if you modify the
+Program, your modified version must prominently offer all users
+interacting with it remotely through a computer network (if your version
+supports such interaction) an opportunity to receive the Corresponding
+Source of your version by providing access to the Corresponding Source
+from a network server at no charge, through some standard or customary
+means of facilitating copying of software. This Corresponding Source
+shall include the Corresponding Source for any work covered by version 3
+of the GNU General Public License that is incorporated pursuant to the
+following paragraph.
+
+ Notwithstanding any other provision of this License, you have
+permission to link or combine any covered work with a work licensed
+under version 3 of the GNU General Public License into a single
+combined work, and to convey the resulting work. The terms of this
+License will continue to apply to the part which is the covered work,
+but the work with which it is combined will remain governed by version
+3 of the GNU General Public License.
+
+ 14. Revised Versions of this License.
+
+ The Free Software Foundation may publish revised and/or new versions of
+the GNU Affero General Public License from time to time. Such new versions
+will be similar in spirit to the present version, but may differ in detail to
+address new problems or concerns.
+
+ Each version is given a distinguishing version number. If the
+Program specifies that a certain numbered version of the GNU Affero General
+Public License "or any later version" applies to it, you have the
+option of following the terms and conditions either of that numbered
+version or of any later version published by the Free Software
+Foundation. If the Program does not specify a version number of the
+GNU Affero General Public License, you may choose any version ever published
+by the Free Software Foundation.
+
+ If the Program specifies that a proxy can decide which future
+versions of the GNU Affero General Public License can be used, that proxy's
+public statement of acceptance of a version permanently authorizes you
+to choose that version for the Program.
+
+ Later license versions may give you additional or different
+permissions. However, no additional obligations are imposed on any
+author or copyright holder as a result of your choosing to follow a
+later version.
+
+ 15. Disclaimer of Warranty.
+
+ THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY
+APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT
+HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY
+OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,
+THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM
+IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF
+ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
+
+ 16. Limitation of Liability.
+
+ IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
+WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS
+THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY
+GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE
+USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF
+DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD
+PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),
+EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF
+SUCH DAMAGES.
+
+ 17. Interpretation of Sections 15 and 16.
+
+ If the disclaimer of warranty and limitation of liability provided
+above cannot be given local legal effect according to their terms,
+reviewing courts shall apply local law that most closely approximates
+an absolute waiver of all civil liability in connection with the
+Program, unless a warranty or assumption of liability accompanies a
+copy of the Program in return for a fee.
+
+ END OF TERMS AND CONDITIONS
+
+ How to Apply These Terms to Your New Programs
+
+ If you develop a new program, and you want it to be of the greatest
+possible use to the public, the best way to achieve this is to make it
+free software which everyone can redistribute and change under these terms.
+
+ To do so, attach the following notices to the program. It is safest
+to attach them to the start of each source file to most effectively
+state the exclusion of warranty; and each file should have at least
+the "copyright" line and a pointer to where the full notice is found.
+
+
+ Copyright (C)
+
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU Affero General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU Affero General Public License for more details.
+
+ You should have received a copy of the GNU Affero General Public License
+ along with this program. If not, see .
+
+Also add information on how to contact you by electronic and paper mail.
+
+ If your software can interact with users remotely through a computer
+network, you should also make sure that it provides a way for users to
+get its source. For example, if your program is a web application, its
+interface could display a "Source" link that leads users to an archive
+of the code. There are many ways you could offer source, and different
+solutions will be better for different programs; see section 13 for the
+specific requirements.
+
+ You should also get your employer (if you work as a programmer) or school,
+if any, to sign a "copyright disclaimer" for the program, if necessary.
+For more information on this, and how to apply and follow the GNU AGPL, see
+.
+
+Additional permission under GNU Affero General Public License version 3
+section 7
+
+If you modify this Program, or any covered work, by linking or combining it with
+other code, such other code is not for that reason alone subject to any of the
+requirements of the GNU Affero GPL version 3 as long as this Program is the same
+Program as licensed from Suomen Kanuuna Oy without any additional modifications.
+
+Supplemented terms under GNU Affero General Public License version 3
+section 7
+
+Origin of the software must be attributed to Suomen Kanuuna Oy. Any modified
+versions must be marked as "Modified version of" The Program.
+
+Names of the licensors and authors may not be used for publicity purposes.
+
+No rights are granted for use of trade names, trademarks, or service marks
+which are in The Program if any.
+
+Licensee must indemnify licensors and authors for any liability that these
+contractual assumptions impose on licensors and authors.
+
+To the extent this program is licensed as part of the Commercial versions of
+Teragrep, the applicable Commercial License may apply to this file if you as
+a licensee so wish it.
diff --git a/README.adoc b/README.adoc
new file mode 100644
index 0000000..9c5cfb9
--- /dev/null
+++ b/README.adoc
@@ -0,0 +1,93 @@
+:opening-bracket: [
+:closing-bracket: ]
+
+= HTTP Event Capture, HEC, to Syslog
+[![Build Status](https://scan.coverity.com/projects/24515/badge.svg)](https://scan.coverity.com/projects/cfe_16)
+
+== License
+AGPLv3 with link:https://github.com/teragrep/cfe_16/blob/master/LICENSE#L665-L670[additional permissions] granted in the license.
+
+== Compiling
+
+[source, shell script]
+----
+mvn clean verify package
+----
+
+== Running
+
+=== Configuration
+
+Default properties location is at config/application.properties
+
+Supported protocols are RELP, TCP, UDP.
+
+[source, properties]
+----
+syslog.server.host=127.0.0.1
+syslog.server.port=601
+syslog.server.protocol=RELP
+max.channels=1000000
+max.ack.value=1000000
+max.ack.age=20000
+max.session.age=30000
+poll.time=300000
+config.poll.time=5000
+spring.devtools.add-properties=false
+server.print.times=true
+----
+
+NOTE: It is advised to use RELP and rsyslog for reception for data durability.
+
+=== Execution
+
+[source, shell script]
+----
+java -jar target/cfe_16.jar
+----
+
+== Testing
+
+=== Performance
+Performance test client execution
+
+Test client is located in class com.teragrep.cfe_16.TestClient
+
+TestClient parameters:
+
+. cfe_16 server hostname
+. cfe_16 server port
+. number of threads
+. number of loops per thread
+
+Connect to Spring embedded Tomcat at localhost:8080, and instantiate one thread
+doing the HTTP requests.
+
+[source, shell script]
+----
+java -classpath target/classes com.teragrep.cfe_16.TestClient localhost 8080 1 4
+----
+
+NOTE: It is advised to warm up the JVM before reporting the results by running
+a warm-up loop with intended amount of threads and 50 loops per thread.
+
+== Contributing
+
+// Change the repository name in the issues link to match with your project's name
+
+You can involve yourself with our project by https://github.com/teragrep/cfe_16/issues/new/choose[opening an issue] or submitting a pull request.
+
+Contribution requirements:
+
+. *All changes must be accompanied by a new or changed test.* If you think testing is not required in your pull request, include a sufficient explanation as why you think so.
+. Security checks must pass
+. Pull requests must align with the principles and http://www.extremeprogramming.org/values.html[values] of extreme programming.
+. Pull requests must follow the principles of Object Thinking and Elegant Objects (EO).
+
+Read more in our https://github.com/teragrep/teragrep/blob/main/contributing.adoc[Contributing Guideline].
+
+=== Contributor License Agreement
+
+Contributors must sign https://github.com/teragrep/teragrep/blob/main/cla.adoc[Teragrep Contributor License Agreement] before a pull request is accepted to organization's repositories.
+
+You need to submit the CLA only once. After submitting the CLA you can contribute to all Teragrep's repositories.
diff --git a/config/application.properties b/config/application.properties
new file mode 100644
index 0000000..f609186
--- /dev/null
+++ b/config/application.properties
@@ -0,0 +1,11 @@
+syslog.server.host=127.0.0.1
+syslog.server.port=1235
+syslog.server.protocol=relp
+max.channels=1000000
+max.ack.value=1000000
+max.ack.age=20000
+max.session.age=30000
+poll.time=300000
+config.poll.time=5000
+spring.devtools.add-properties=false
+server.print.times=true
diff --git a/entrypoint.sh b/entrypoint.sh
new file mode 100644
index 0000000..066a61e
--- /dev/null
+++ b/entrypoint.sh
@@ -0,0 +1,11 @@
+#!/usr/bin/env bash
+
+if [ -z ${XMS} ]; then
+ XMS=256m
+fi
+if [ -z ${XMX} ]; then
+ XMX=512m
+fi
+echo "Xms=${XMS}"
+echo "Xmx=${XMX}"
+java -jar /opt/teragrep/cfe_16/lib/cfe_16.jar -Xms${XMS} -Xmx${XMX} -Dspring.config.location="file://${CONFIG_PATH}"
diff --git a/pom.xml b/pom.xml
new file mode 100644
index 0000000..4195e19
--- /dev/null
+++ b/pom.xml
@@ -0,0 +1,328 @@
+
+
+ jar
+ 4.0.0
+ cfe_16
+ ${revision}${sha1}${changelist}
+ cfe_16
+ cfe_16
+ com.teragrep
+
+ UTF-8
+ 1.8
+ ${java.version}
+ ${java.version}
+ 0.0.1
+ -SNAPSHOT
+
+ 1.9.21.1
+ 2.23.1
+ 2.7.18
+ 2.7.4
+ 5.10.2
+ 1.10.2
+ 4.0.1
+ 3.0.0
+ 4.0.1
+ 2.16.2
+
+
+
+ org.apache.logging.log4j
+ log4j-api
+ ${log4j.version}
+
+
+ org.apache.logging.log4j
+ log4j-core
+ ${log4j.version}
+
+
+ org.apache.logging.log4j
+ log4j-slf4j-impl
+ ${log4j.version}
+
+
+
+ javax.servlet
+ javax.servlet-api
+ ${javax.servlet.version}
+
+
+ com.teragrep
+ rlp_01
+ ${rlp_01.version}
+
+
+ com.teragrep
+ rlp_03
+ ${rlp_03.version}
+ test
+
+
+ org.springframework.boot
+ spring-boot-starter
+ ${spring.boot.version}
+
+
+ org.springframework.boot
+ spring-boot-starter-logging
+
+
+
+
+ org.springframework.boot
+ spring-boot-starter-web
+ ${spring.boot.version}
+
+
+ org.springframework.boot
+ spring-boot-starter-test
+ ${spring.boot.version}
+ test
+
+
+ org.springframework.boot
+ spring-boot-starter-log4j2
+ ${spring.boot.version}
+
+
+ org.springframework.boot
+ spring-boot-devtools
+ ${spring.boot.version}
+ test
+
+
+ org.springframework.boot
+ spring-boot-starter-actuator
+ ${spring.boot.version}
+
+
+ org.springframework.session
+ spring-session-core
+ ${spring.session.version}
+
+
+ org.springframework.session
+ spring-session-jdbc
+ ${spring.session.version}
+
+
+ org.aspectj
+ aspectjrt
+ ${aspectj.version}
+ provided
+
+
+ org.aspectj
+ aspectjweaver
+ ${aspectj.version}
+ provided
+
+
+ com.fasterxml.jackson.core
+ jackson-databind
+ ${jackson.version}
+
+
+ com.fasterxml.jackson.core
+ jackson-annotations
+ ${jackson.version}
+
+
+ com.cloudbees
+ syslog-java-client
+ 1.1.7
+
+
+ com.google.code.gson
+ gson
+ 2.10.1
+
+
+ systems.manifold
+ manifold-all
+ 2024.1.6
+
+
+ org.powermock
+ powermock-api-mockito2
+ 2.0.9
+ test
+
+
+ org.apache.felix
+ maven-bundle-plugin
+ 5.1.9
+
+
+ org.junit.jupiter
+ junit-jupiter-api
+ ${junit.version}
+ test
+
+
+ org.junit.vintage
+ junit-vintage-engine
+ ${junit.version}
+ test
+
+
+ org.junit.jupiter
+ junit-jupiter-engine
+ ${junit.version}
+ test
+
+
+ org.junit.platform
+ junit-platform-commons
+ ${junit.platform.version}
+ test
+
+
+
+ ${project.basedir}/target
+ cfe_16
+
+
+ maven-enforcer-plugin
+ 3.4.1
+
+
+ enforce
+ none
+
+
+ enforce-maven
+
+ enforce
+
+
+
+
+ 3.2.5
+
+
+ 1.8
+
+
+
+
+
+
+
+ org.apache.rat
+ apache-rat-plugin
+ 0.15
+ false
+
+
+ test
+
+ check
+
+
+
+
+ false
+
+
+ Also allow the license url to be https.
+
+ Copyright (C) 2021 Suomen Kanuuna Oy
+
+
+
+ true
+ false
+
+
+ .git/**
+ .gitattributes
+ .gitignore
+ .gitmodules
+
+ .github/workflows/*.yml
+ .github/workflows/*.yaml
+ .github/ISSUE_TEMPLATE/*
+ toolchains.xml
+ settings.xml
+ rpm/rpm.pom.xml
+
+ README.adoc
+ README.md
+
+ src/main/java/com/teragrep/cfe_16/ThirdParty/SyslogMessageSender/SyslogMessageSender.java
+
+ src/main/resources/*
+ src/test/resources/cfe-16-test-plan.jmx
+
+ rpm/rpm.pom.xml
+ Dockerfile
+ config/*
+ *.sh
+
+
+
+
+ org.springframework.boot
+ spring-boot-maven-plugin
+ ${spring.boot.version}
+
+ com.teragrep.cfe_16.Cfe16Application
+ ${settings.localRepository}/org/aspectj/aspectjweaver/${aspectj.version}/aspectjweaver-${aspectj.version}.jar
+
+
+
+
+ repackage
+
+
+
+
+
+ org.apache.maven.plugins
+ maven-surefire-plugin
+ 3.2.5
+
+ 1
+
+
+
+ org.apache.maven.plugins
+ maven-deploy-plugin
+ 3.1.1
+
+
+ org.apache.maven.plugins
+ maven-failsafe-plugin
+ 3.2.5
+
+ ${project.build.outputDirectory}
+
+
+
+ integration-tests
+
+ integration-test
+
+
+
+ verify
+
+ verify
+
+
+
+
+
+
+
+
+ github
+ https://maven.pkg.github.com/${env.GITHUB_REPOSITORY}
+
+
+
diff --git a/rpm/rpm.pom.xml b/rpm/rpm.pom.xml
new file mode 100644
index 0000000..7ce9c39
--- /dev/null
+++ b/rpm/rpm.pom.xml
@@ -0,0 +1,133 @@
+
+
+ rpm
+ 4.0.0
+ cfe_16
+ ${revision}${sha1}${changelist}
+ cfe_16
+ cfe_16
+ com.teragrep
+ https://teragrep.com
+
+ UTF-8
+ 1.8
+ 1.8
+ 1.8
+ 0.0.1
+ -SNAPSHOT
+
+
+
+ scm:git:https://github.com/teragrep/cfe_16.git
+ scm:git:git@github.com:teragrep/cfe_16.git
+ https://github.com/teragrep/cfe_16/tree/master
+
+
+ ${project.basedir}/target
+
+
+ maven-enforcer-plugin
+ 3.4.1
+
+
+ enforce
+ none
+
+
+ enforce-maven
+
+ enforce
+
+
+
+
+ 3.2.5
+
+
+ 1.8
+
+
+
+
+
+
+
+ org.codehaus.mojo
+ rpm-maven-plugin
+ 2.2.0
+ true
+
+
+ default-rpm
+
+ rpm
+
+ package
+
+
+
+ ${project.groupId}-${project.artifactId}
+ ${project.groupId}-${project.artifactId}
+ ${project.version}
+ ${env.GITHUB_RUN_NUMBER}
+ Proprietary
+ Fail-Safe Log Management Suite
+ https://fail-safe.net/
+ Fail-Safe <servicedesk@fail-safe.net>
+ Fail-Safe/LogManagementSuite
+ ${project.groupId}-${project.artifactId}
+ false
+ srv-cfe_16
+ srv-cfe_16
+ 0644
+ 0755
+
+ _build_id_links none
+
+
+
+ /opt/teragrep/${project.artifactId}/lib
+ true
+ 755
+ 755
+ srv-cfe_16
+ srv-cfe_16
+ true
+
+
+ ${project.basedir}/../target/cfe_16.jar
+
+
+
+
+
+ java-1.8.0-openjdk
+
+
+
+
+
+
+
+ org.apache.maven.plugins
+ maven-surefire-plugin
+ 3.2.5
+
+ true
+
+
+
+
+
+
+ github
+ https://maven.pkg.github.com/${env.GITHUB_REPOSITORY}
+
+
+
diff --git a/run-client.sh b/run-client.sh
new file mode 100755
index 0000000..7cbdd9b
--- /dev/null
+++ b/run-client.sh
@@ -0,0 +1,3 @@
+#!/bin/bash
+java -classpath target/classes comt.teragrep.cfe_16.TestClient $1 $2 $3 $4
+
diff --git a/run-server.sh b/run-server.sh
new file mode 100755
index 0000000..0356167
--- /dev/null
+++ b/run-server.sh
@@ -0,0 +1,2 @@
+mvn spring-boot:run -Dspring.config.location="file://src/main/resources/application.properties"
+
diff --git a/src/main/java/com/teragrep/cfe_16/AckManager.java b/src/main/java/com/teragrep/cfe_16/AckManager.java
new file mode 100644
index 0000000..ec9726b
--- /dev/null
+++ b/src/main/java/com/teragrep/cfe_16/AckManager.java
@@ -0,0 +1,468 @@
+/*
+ * HTTP Event Capture to RFC5424 CFE_16
+ * Copyright (C) 2021 Suomen Kanuuna Oy
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see .
+ *
+ *
+ * Additional permission under GNU Affero General Public License version 3
+ * section 7
+ *
+ * If you modify this Program, or any covered work, by linking or combining it
+ * with other code, such other code is not for that reason alone subject to any
+ * of the requirements of the GNU Affero GPL version 3 as long as this Program
+ * is the same Program as licensed from Suomen Kanuuna Oy without any additional
+ * modifications.
+ *
+ * Supplemented terms under GNU Affero General Public License version 3
+ * section 7
+ *
+ * Origin of the software must be attributed to Suomen Kanuuna Oy. Any modified
+ * versions must be marked as "Modified version of" The Program.
+ *
+ * Names of the licensors and authors may not be used for publicity purposes.
+ *
+ * No rights are granted for use of trade names, trademarks, or service marks
+ * which are in The Program if any.
+ *
+ * Licensee must indemnify licensors and authors for any liability that these
+ * contractual assumptions impose on licensors and authors.
+ *
+ * To the extent this program is licensed as part of the Commercial versions of
+ * Teragrep, the applicable Commercial License may apply to this file if you as
+ * a licensee so wish it.
+ */
+
+package com.teragrep.cfe_16;
+
+import com.fasterxml.jackson.databind.JsonNode;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.teragrep.cfe_16.bo.Ack;
+import com.teragrep.cfe_16.config.Configuration;
+import com.teragrep.cfe_16.exceptionhandling.InternalServerErrorException;
+import com.teragrep.cfe_16.exceptionhandling.ServerIsBusyException;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Component;
+
+import javax.annotation.PostConstruct;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.Map;
+
+/*
+ * Manager that handles the acknowledgement status of the sent events (acks).
+ * A background thread is used to clean up NRU ACK objects.
+ *
+ * This class is thread safe.
+ *
+ */
+@Component
+public class AckManager implements Runnable, LifeCycle {
+ private static final Logger LOGGER = LoggerFactory.getLogger(AckManager.class);
+ /**
+ * A class that encapsulates state of individual channels regarding to ACKs.
+ *
+ */
+ private class State {
+ private int currentAckValue;
+ private Ack ackToCompare;
+ private Map ackMap;
+
+ public State() {
+ this.currentAckValue = 0;
+ this.ackToCompare = new Ack();
+ this.ackMap = new HashMap();
+ }
+
+ public int getCurrentAckValue() {
+ return this.currentAckValue;
+ }
+
+ public void setCurrentAckValue(int currentAckValue) {
+ this.currentAckValue = currentAckValue;
+ }
+
+ public Ack getAckToCompare() {
+ return this.ackToCompare;
+ }
+
+ public void setAckToCompare(Ack ackToCompare) {
+ this.ackToCompare = ackToCompare;
+ }
+
+ public Map getAckMap() {
+ return this.ackMap;
+ }
+
+ @Override
+ public String toString() {
+ return "State [currentAckValue=" + this.currentAckValue + ", ackToCompare=" + this.ackToCompare
+ + ", ackMap=" + this.ackMap + "]";
+ }
+ }
+
+ /**
+ * Does the JSON <-> Java conversions.
+ */
+ private final ObjectMapper objectMapper;
+
+ /**
+ * A hash for mapping channels to ACK status objects.
+ */
+ private final Map ackStates;
+
+ /**
+ * The background thread for cleaning up ACKs.
+ */
+ private Thread cleanerThread;
+
+ @Autowired
+ private Configuration configuration;
+
+ /**
+ * An empty constructor for Spring @Autowired annotation.
+ */
+ public AckManager() {
+ this.objectMapper = new ObjectMapper();
+ this.ackStates = Collections.synchronizedMap(new HashMap());
+ }
+
+ @Override
+ @PostConstruct
+ public void start() {
+ this.cleanerThread = new Thread(this);
+ this.cleanerThread.start();
+ }
+
+ /**
+ * Let's interrupt the cleaner thread from its eternal run().
+ */
+ @Override
+ public void stop() {
+ this.cleanerThread.interrupt();
+ }
+
+ /**
+ * A private Accessor for the State object indexed by the given auth token and channel.
+ * If no State object is found, a new object is created and added to the map.
+ *
+ * @param authToken
+ * @param channel
+ * @return
+ */
+ private State getOrCreateState(String authToken, String channel) {
+ LOGGER.debug("Getting or creating state for channel <{}>", channel);
+ String key = authToken + channel;
+ State state = this.ackStates.get(key);
+ if (state == null) {
+ state = new State();
+ this.ackStates.put(key, state);
+ }
+ LOGGER.debug("Created state <{}> for channel <{}>", state, channel);
+ return state;
+ }
+
+ /**
+ * This method has to be called first before calling any other Ack related
+ * methods.
+ *
+ * @param authToken
+ * @param channel
+ */
+ public void initializeContext(String authToken, String channel) {
+ LOGGER.debug("Initializing context for channel <{}>", channel);
+ String key = authToken + channel;
+ if (!this.ackStates.containsKey(key)) {
+ LOGGER.debug("Adding new state to channel <{}>", channel);
+ State state = new State();
+ this.ackStates.put(key, state);
+ }
+ }
+
+ /*
+ * Assignes an Ack value for the event. Checks it there are acks still available
+ * for the channel If there are no Acks available, throws ServerIsBusyException.
+ * Uses the ackToCompare variable to check if Ack with a current value is in the
+ * Ack list and increases the Ack value until a suitable Ack value is found and
+ * when it is found, a new Ack with the current Ack value as an id is created.
+ */
+ public boolean incrementAckValue(String authToken, String channel) {
+ String key = authToken + channel;
+ State state = this.ackStates.get(key);
+ if (state == null) {
+ return false;
+ }
+
+ if (!acksAvailable(state)) {
+ throw new ServerIsBusyException();
+ }
+
+ int currentAckValue;
+ synchronized (state) {
+ currentAckValue = state.getCurrentAckValue();
+ Ack ackToCompare = state.getAckToCompare();
+ ackToCompare.setId(currentAckValue);
+ state.setAckToCompare(ackToCompare);
+ Map ackMap = state.getAckMap();
+ while (ackMap.containsKey(ackToCompare.getId())) {
+ currentAckValue++;
+ ackToCompare = state.getAckToCompare();
+ ackToCompare.setId(currentAckValue);
+ if (currentAckValue > this.configuration.getMaxAckValue()) {
+ currentAckValue = 0;
+ }
+ state.setAckToCompare(ackToCompare);
+ }
+
+ currentAckValue++;
+ if (currentAckValue > this.configuration.getMaxAckValue()) {
+ currentAckValue = 0;
+ }
+ state.setCurrentAckValue(currentAckValue);
+ }
+ return true;
+ }
+
+ /*
+ * Uses the help Ack object to find the Ack object from the Ack set and sets the
+ * acknowledgement status of the Ack as true.
+ */
+ public boolean acknowledge(String authToken, String channel, int ackId) {
+ String key = authToken + channel;
+ State state = this.ackStates.get(key);
+ LOGGER.debug("Acknowledging ackId <{}> on channel <{}>", ackId, channel);
+ if (state == null) {
+ throw new IllegalStateException("An Ack cannot be acknowledge before it is added to the Ack list.");
+ }
+ synchronized (state) {
+ Map ackMap = state.getAckMap();
+ Ack ack = ackMap.get(ackId);
+ if (ack == null) {
+ throw new InternalServerErrorException("Couldn't set the acknowledge status for Ack ID " + ackId);
+ }
+ ack.acknowledge();
+ return true;
+ }
+ }
+
+ /**
+ * Adds a new Ack object for given channel. If this is the first time a channel
+ * is assigned a new Ack, a new State object is created.
+ *
+ * @param channel
+ * @param ack
+ */
+ public boolean addAck(String authToken, String channel, Ack ack) {
+ String key = authToken + channel;
+ State state = this.ackStates.get(key);
+ if (state == null) {
+ throw new InternalServerErrorException("No State for key " + key);
+ }
+ synchronized (state) {
+ state.ackMap.put(ack.getId(), ack);
+ return true;
+ }
+ }
+
+ /**
+ * Checks if the Ack with a given id is acknowledged.
+ */
+ public boolean isAckAcknowledged(String authToken, String channel, int ackId) {
+ String key = authToken + channel;
+ State state = this.ackStates.get(key);
+ if (state == null) {
+ throw new InternalServerErrorException("No State for key " + key);
+ }
+ synchronized (state) {
+ Map ackMap = state.getAckMap();
+ Ack ack = ackMap.get(ackId);
+ if (ack == null) {
+ return false;
+ }
+ ack.acknowledge();
+ return true;
+ }
+ }
+
+ /**
+ * Returns the Ack statuses of requested Ack id:s as a JSON node. JSON node with
+ * the id:s is given as a parameter. Example: {"acks": [1,3,4]}
+ */
+ public JsonNode getRequestedAckStatuses(String authToken, String channel, JsonNode requestedAcksInJson) {
+ JsonNode jsonNode = this.objectMapper.createObjectNode();
+ if (requestedAcksInJson == null) {
+ return jsonNode;
+ }
+
+ /*
+ * Checks that the JSON parameter is given, and that there is an Ack node which
+ * is an array. Saves the requested ack ids in an int array.
+ */
+ int[] requestedAckIds = null;
+ if (requestedAcksInJson.get("acks") != null && requestedAcksInJson.get("acks").isArray()) {
+ requestedAckIds = this.objectMapper.convertValue(requestedAcksInJson.get("acks"), int[].class);
+ }
+
+ /*
+ * Goes through the requested Ack id:s, checks if they have been assigned to an
+ * Ack object in the Ack list. If an Ack is found, the Ack id and the
+ * acknowledgement status of that Ack is saved in "ackStatuses" JSON node. If
+ * Ack is not found in the list, the Ack id and acknowledgement status "false"
+ * is added to the JSON node.
+ */
+
+ Map ackStatuses = new HashMap();
+ if (requestedAckIds != null) {
+ String key = authToken + channel;
+ State state = this.ackStates.get(key);
+ if (state == null) {
+ return null;
+ }
+ synchronized (state) {
+ Map ackMap = state.getAckMap();
+ for (int i = 0; i < requestedAckIds.length; i++) {
+ int ackId = requestedAckIds[i];
+ Ack ack = ackMap.get(ackId);
+ if (ack == null) {
+ ackStatuses.put(ackId, false);
+ } else {
+ ackStatuses.put(ackId, ack.isAcknowledged());
+ ackMap.remove(ackId);
+ }
+ }
+ }
+ }
+ jsonNode = this.objectMapper.convertValue(ackStatuses, JsonNode.class);
+ return jsonNode;
+ }
+
+ /*
+ * Checks if there is room in the Ack list for a new entry
+ *
+ * Not thread safe, needs external synchronization.
+ */
+ private boolean acksAvailable(State state) {
+ Map ackMap = state.getAckMap();
+ int ackMapSize = ackMap.size();
+ int maxAckValue = this.configuration.getMaxAckValue();
+ if (ackMapSize > maxAckValue) {
+ return false;
+ } else {
+ return true;
+ }
+ }
+
+ /*
+ * Deletes a given Ack from the Ack list.
+ *
+ * This is an O(n) operation..
+ */
+ public boolean deleteAckFromList(String authToken, String channel, Ack ack) {
+ String key = authToken + channel;
+ State state = this.ackStates.get(key);
+ if (state == null) {
+ throw new InternalServerErrorException("No State for key " + key);
+ }
+ synchronized (state) {
+ state.getAckMap().remove(ack.getId());
+ return true;
+ }
+ }
+
+ public void run() {
+
+ while (true) {
+ try {
+ LOGGER.debug("Sleeping for <{}> while waiting for polls", this.configuration.getPollTime());
+ Thread.sleep(this.configuration.getPollTime());
+ } catch (InterruptedException e) {
+ break;
+ }
+
+ for (String key : this.ackStates.keySet()) {
+ State state = this.ackStates.get(key);
+
+ synchronized (state) {
+ Map ackMap = state.getAckMap();
+ Iterator iterator = ackMap.values().iterator();
+ while (iterator.hasNext()) {
+ Ack ack = iterator.next();
+ long thresholdInLong = ack.getLastUsedTimestamp() + this.configuration.getMaxAckAge();
+
+ /**
+ * If the Ack object is too old we'll remove it from the Ack set.
+ */
+ long now = System.currentTimeMillis();
+ if (now >= thresholdInLong) {
+ iterator.remove();
+ }
+ }
+ }
+ }
+ }
+ }
+
+ /**
+ * Returns the size of the Ack set of given channel.
+ *
+ * @param channel
+ * @return
+ */
+ public int getAckListSize(String authToken, String channel) {
+ String key = authToken + channel;
+ State state = this.ackStates.get(key);
+ if (state == null) {
+ throw new InternalServerErrorException("No State for key " + key);
+ }
+ synchronized (state) {
+ return state.getAckMap().size();
+ }
+ }
+
+ /**
+ * Returns the unmodifiable set of Acks for a channel.
+ *
+ * @param channel
+ * @return
+ */
+ public Map getAckList(String authToken, String channel) {
+ String key = authToken + channel;
+ State state = this.ackStates.get(key);
+ if (state == null) {
+ throw new InternalServerErrorException("No State for key " + key);
+ }
+ synchronized (state) {
+ return state.getAckMap();
+ }
+ }
+
+ /**
+ * Returns the current Ack value for given token and channel.
+ * A new State is created, so this method must be called
+ * first before other Ack manipulating methods are called.
+ *
+ * @param authToken
+ * @param channel
+ * @return
+ */
+ public int getCurrentAckValue(String authToken, String channel) {
+ State state = this.getOrCreateState(authToken, channel);
+ synchronized (state) {
+ return state.getCurrentAckValue();
+ }
+ }
+}
diff --git a/src/main/java/com/teragrep/cfe_16/Cfe16Application.java b/src/main/java/com/teragrep/cfe_16/Cfe16Application.java
new file mode 100644
index 0000000..22cfc2b
--- /dev/null
+++ b/src/main/java/com/teragrep/cfe_16/Cfe16Application.java
@@ -0,0 +1,64 @@
+/*
+ * HTTP Event Capture to RFC5424 CFE_16
+ * Copyright (C) 2021 Suomen Kanuuna Oy
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see .
+ *
+ *
+ * Additional permission under GNU Affero General Public License version 3
+ * section 7
+ *
+ * If you modify this Program, or any covered work, by linking or combining it
+ * with other code, such other code is not for that reason alone subject to any
+ * of the requirements of the GNU Affero GPL version 3 as long as this Program
+ * is the same Program as licensed from Suomen Kanuuna Oy without any additional
+ * modifications.
+ *
+ * Supplemented terms under GNU Affero General Public License version 3
+ * section 7
+ *
+ * Origin of the software must be attributed to Suomen Kanuuna Oy. Any modified
+ * versions must be marked as "Modified version of" The Program.
+ *
+ * Names of the licensors and authors may not be used for publicity purposes.
+ *
+ * No rights are granted for use of trade names, trademarks, or service marks
+ * which are in The Program if any.
+ *
+ * Licensee must indemnify licensors and authors for any liability that these
+ * contractual assumptions impose on licensors and authors.
+ *
+ * To the extent this program is licensed as part of the Commercial versions of
+ * Teragrep, the applicable Commercial License may apply to this file if you as
+ * a licensee so wish it.
+ */
+
+package com.teragrep.cfe_16;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.boot.SpringApplication;
+import org.springframework.boot.autoconfigure.SpringBootApplication;
+import org.springframework.context.annotation.ComponentScan;
+
+@SpringBootApplication
+@ComponentScan({ "com.teragrep.cfe_16" })
+public class Cfe16Application {
+ private static final Logger LOGGER = LoggerFactory.getLogger(Cfe16Application.class);
+ public static void main(String[] args) {
+ LOGGER.info("Starting Cfe16Application...");
+ SpringApplication.run(Cfe16Application.class, args);
+ //Runtime.getRuntime().addShutdownHook(new ShutdownHook());
+ }
+}
diff --git a/src/main/java/com/teragrep/cfe_16/Converter.java b/src/main/java/com/teragrep/cfe_16/Converter.java
new file mode 100644
index 0000000..0832339
--- /dev/null
+++ b/src/main/java/com/teragrep/cfe_16/Converter.java
@@ -0,0 +1,183 @@
+/*
+ * HTTP Event Capture to RFC5424 CFE_16
+ * Copyright (C) 2021 Suomen Kanuuna Oy
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see .
+ *
+ *
+ * Additional permission under GNU Affero General Public License version 3
+ * section 7
+ *
+ * If you modify this Program, or any covered work, by linking or combining it
+ * with other code, such other code is not for that reason alone subject to any
+ * of the requirements of the GNU Affero GPL version 3 as long as this Program
+ * is the same Program as licensed from Suomen Kanuuna Oy without any additional
+ * modifications.
+ *
+ * Supplemented terms under GNU Affero General Public License version 3
+ * section 7
+ *
+ * Origin of the software must be attributed to Suomen Kanuuna Oy. Any modified
+ * versions must be marked as "Modified version of" The Program.
+ *
+ * Names of the licensors and authors may not be used for publicity purposes.
+ *
+ * No rights are granted for use of trade names, trademarks, or service marks
+ * which are in The Program if any.
+ *
+ * Licensee must indemnify licensors and authors for any liability that these
+ * contractual assumptions impose on licensors and authors.
+ *
+ * To the extent this program is licensed as part of the Commercial versions of
+ * Teragrep, the applicable Commercial License may apply to this file if you as
+ * a licensee so wish it.
+ */
+
+package com.teragrep.cfe_16;
+
+import com.cloudbees.syslog.Facility;
+import com.cloudbees.syslog.SDElement;
+import com.cloudbees.syslog.Severity;
+import com.cloudbees.syslog.SyslogMessage;
+import com.teragrep.cfe_16.bo.HeaderInfo;
+import com.teragrep.cfe_16.bo.HttpEventData;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.stereotype.Component;
+
+/*
+ * Converts HTTP Event Data into a Syslog message.
+ *
+ * This class is NOT thread safe!
+ *
+ */
+@Component
+public class Converter {
+ private static final Logger LOGGER = LoggerFactory.getLogger(Converter.class);
+ private Severity severity;
+ private Facility facility;
+
+ private SDElement metadataSDE;
+ private SDElement headerSDE;
+
+ public SyslogMessage httpToSyslog(HttpEventData httpEventData, HeaderInfo headerInfo) {
+
+ setEventSeverity();
+ setEventFacility();
+
+ setStructuredDataParams(httpEventData);
+ setHeaderSDE(headerInfo);
+
+ SyslogMessage syslogMessage;
+ if (httpEventData.isTimeParsed()) {
+
+ /*
+ * Creates a Syslogmessage with a time stamp
+ */
+ LOGGER.debug("Creating new syslog message with timestamp");
+ syslogMessage = new SyslogMessage().withTimestamp(httpEventData.getTimeAsLong()).withSeverity(severity)
+ .withAppName("capsulated").withHostname("cfe_16").withFacility(facility).withSDElement(metadataSDE)
+ .withSDElement(headerSDE).withMsg(httpEventData.getEvent());
+
+ } else {
+ /*
+ * Creates a Syslogmessage without timestamp, because the time is already given
+ * in the request.
+ */
+ LOGGER.debug("Creating new syslog message without timestamp");
+ syslogMessage = new SyslogMessage().withSeverity(severity).withAppName("capsulated").withHostname("cfe_16")
+ .withFacility(facility).withSDElement(metadataSDE).withSDElement(headerSDE)
+ .withMsg(httpEventData.getEvent());
+ }
+
+ return syslogMessage;
+ }
+
+ /*
+ * Event severity is coded to always be INFORMATIONAL
+ */
+ private void setEventSeverity() {
+ severity = Severity.INFORMATIONAL;
+ }
+
+ /*
+ * Event facility is coded to always be USER
+ */
+ private void setEventFacility() {
+ facility = Facility.USER;
+ }
+
+ /*
+ * Gets the data from the HTTP Event Data and adds it to SD Element as SD
+ * Parameters.
+ */
+ private void setStructuredDataParams(HttpEventData eventData) {
+ LOGGER.debug("Setting Structured Data params");
+ metadataSDE = new SDElement("cfe_16-metadata@48577");
+
+ if (eventData.getAuthenticationToken() != null) {
+ LOGGER.debug("Setting authentication token");
+ metadataSDE.addSDParam("authentication_token", eventData.getAuthenticationToken());
+ }
+
+ if (eventData.getChannel() != null) {
+ LOGGER.debug("Setting channel");
+ metadataSDE.addSDParam("channel", eventData.getChannel());
+ }
+
+ if (eventData.getAckID() != null) {
+ LOGGER.debug("Setting ack id");
+ metadataSDE.addSDParam("ack_id", String.valueOf(eventData.getAckID()));
+ }
+
+ if (eventData.getTimeSource() != null) {
+ LOGGER.debug("Setting time source");
+ metadataSDE.addSDParam("time_source", eventData.getTimeSource());
+ }
+
+ if (eventData.isTimeParsed()) {
+ LOGGER.debug("Setting time_parsed and time");
+ metadataSDE.addSDParam("time_parsed", "true");
+ metadataSDE.addSDParam("time", eventData.getTime());
+ }
+ }
+
+ public SyslogMessage getHeaderInfoSyslogMessage(HeaderInfo headerInfo) {
+
+ SyslogMessage syslogMessage = null;
+ setHeaderSDE(headerInfo);
+ syslogMessage = new SyslogMessage().withSeverity(severity).withAppName("capsulated").withHostname("cfe_16")
+ .withFacility(facility).withSDElement(headerSDE);
+
+ return syslogMessage;
+ }
+
+ private void setHeaderSDE(HeaderInfo headerInfo) {
+ LOGGER.debug("Setting Structured Data headers");
+ headerSDE = new SDElement("cfe_16-origin@48577");
+
+ if (headerInfo.getxForwardedFor() != null) {
+ LOGGER.debug("Adding X-Forwarded-For header to headerSDE");
+ headerSDE.addSDParam("X-Forwarded-For", headerInfo.getxForwardedFor());
+ }
+ if (headerInfo.getxForwardedHost() != null) {
+ LOGGER.debug("Adding X-Forwarder-Host to headerSDE");
+ headerSDE.addSDParam("X-Forwarded-Host", headerInfo.getxForwardedHost());
+ }
+ if (headerInfo.getxForwardedProto() != null) {
+ LOGGER.debug("Adding X-Forwarded-Proto to headerSDE");
+ headerSDE.addSDParam("X-Forwarded-Proto", headerInfo.getxForwardedProto());
+ }
+ }
+}
diff --git a/src/main/java/com/teragrep/cfe_16/EventManager.java b/src/main/java/com/teragrep/cfe_16/EventManager.java
new file mode 100644
index 0000000..4a435c4
--- /dev/null
+++ b/src/main/java/com/teragrep/cfe_16/EventManager.java
@@ -0,0 +1,330 @@
+/*
+ * HTTP Event Capture to RFC5424 CFE_16
+ * Copyright (C) 2021 Suomen Kanuuna Oy
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see .
+ *
+ *
+ * Additional permission under GNU Affero General Public License version 3
+ * section 7
+ *
+ * If you modify this Program, or any covered work, by linking or combining it
+ * with other code, such other code is not for that reason alone subject to any
+ * of the requirements of the GNU Affero GPL version 3 as long as this Program
+ * is the same Program as licensed from Suomen Kanuuna Oy without any additional
+ * modifications.
+ *
+ * Supplemented terms under GNU Affero General Public License version 3
+ * section 7
+ *
+ * Origin of the software must be attributed to Suomen Kanuuna Oy. Any modified
+ * versions must be marked as "Modified version of" The Program.
+ *
+ * Names of the licensors and authors may not be used for publicity purposes.
+ *
+ * No rights are granted for use of trade names, trademarks, or service marks
+ * which are in The Program if any.
+ *
+ * Licensee must indemnify licensors and authors for any liability that these
+ * contractual assumptions impose on licensors and authors.
+ *
+ * To the extent this program is licensed as part of the Commercial versions of
+ * Teragrep, the applicable Commercial License may apply to this file if you as
+ * a licensee so wish it.
+ */
+
+package com.teragrep.cfe_16;
+
+import com.cloudbees.syslog.SyslogMessage;
+import com.fasterxml.jackson.databind.JsonNode;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.fasterxml.jackson.databind.node.ObjectNode;
+import com.google.gson.JsonStreamParser;
+import com.teragrep.cfe_16.bo.Ack;
+import com.teragrep.cfe_16.bo.HeaderInfo;
+import com.teragrep.cfe_16.bo.HttpEventData;
+import com.teragrep.cfe_16.bo.Session;
+import com.teragrep.cfe_16.config.Configuration;
+import com.teragrep.cfe_16.exceptionhandling.EventFieldBlankException;
+import com.teragrep.cfe_16.exceptionhandling.EventFieldMissingException;
+import com.teragrep.cfe_16.exceptionhandling.InternalServerErrorException;
+import com.teragrep.cfe_16.sender.AbstractSender;
+import com.teragrep.cfe_16.sender.SenderFactory;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Component;
+
+import javax.annotation.PostConstruct;
+import java.io.IOException;
+import java.math.BigDecimal;
+import java.util.ArrayList;
+import java.util.List;
+
+/*
+ * Manager that handles the event sent in a request.
+ *
+ */
+@Component
+public class EventManager {
+ private static final Logger LOGGER = LoggerFactory.getLogger(EventManager.class);
+ private final ObjectMapper objectMapper;
+
+ @Autowired
+ private Configuration configuration;
+
+ private AbstractSender sender;
+
+ public EventManager() {
+ this.objectMapper = new ObjectMapper();
+ }
+
+ @PostConstruct
+ public void setupSender() {
+ LOGGER.debug("Setting up sender");
+ try {
+ this.sender = SenderFactory.createSender(this.configuration.getSysLogProtocol(),
+ this.configuration.getSyslogHost(),
+ this.configuration.getSyslogPort());
+ } catch (IOException e) {
+ LOGGER.error("Error creating sender", e);
+ throw new InternalServerErrorException();
+ }
+ }
+
+ /*
+ * Method used when converting data and the channel is specified in the request.
+ * Takes authentication token, all events sent in a request (in JSON format) and
+ * the channel name as string parameters. Returns a JSON node with ack id if
+ * everything is successful. Example: {"text":"Success","code":0,"ackID":0}
+ */
+ public ObjectNode convertData(String authToken,
+ String channel,
+ String allEventsInJson,
+ HeaderInfo headerInfo,
+ AckManager ackManager) {
+ HttpEventData previousEvent = null;
+
+ ackManager.initializeContext(authToken, channel);
+ int ackId = ackManager.getCurrentAckValue(authToken, channel);
+ boolean incremented = ackManager.incrementAckValue(authToken, channel);
+ if (!incremented) {
+ throw new InternalServerErrorException("Ack value couldn't be incremented.");
+ }
+ Ack ack = new Ack(ackId, false);
+ boolean addedAck = ackManager.addAck(authToken, channel, ack);
+ if (!addedAck) {
+ throw new InternalServerErrorException("Ack ID " + ackId + " couldn't be added to the Ack set.");
+ }
+ JsonStreamParser parser = new JsonStreamParser(allEventsInJson);
+
+ /*
+ * There can be multiple events in one request. Here they are handled one by
+ * one. The event is saved in a string variable and is converted into
+ * HttpEventData object. Metadata is assigned to the object. HttpEventData is
+ * converted into SyslogMessage and saved in a list in a RequestInfo object.
+ * After the event is handled, it is assigned as a value to previousEvent
+ * variable.
+ */
+ HttpEventData eventData = null;
+ Converter converter = new Converter();
+ List syslogMessages = new ArrayList();
+ while (parser.hasNext()) {
+ previousEvent = eventData;
+ String jsonObjectStr = parser.next().toString();
+ eventData = verifyJsonData(jsonObjectStr, previousEvent);
+ eventData = assignMetaData(eventData, authToken, channel);
+ SyslogMessage syslogMessage = converter.httpToSyslog(eventData, headerInfo);
+ syslogMessages.add(syslogMessage);
+ }
+
+ /*
+ * SyslogMessage syslogMessage =
+ * converter.getHeaderInfoSyslogMessage(headerInfo);
+ * requestInfo.getConvertedData().add(syslogMessage);
+ */
+ /*
+ * After all the events are sent, previousEvent object is set to null, the
+ * events are sent with the ackManager and ack id and JSON node with an ack id
+ * will be returned informing that the sending of the events has been
+ * successful.
+ */
+ previousEvent = null;
+
+ // create a new object to avoid blocking of threads because
+ // the SyslogMessageSender.sendMessage() is synchronized
+ try {
+ SyslogMessage[] messages = syslogMessages.toArray(new SyslogMessage[syslogMessages.size()]);
+ this.sender.sendMessages(messages);
+ } catch (IOException e) {
+ throw new InternalServerErrorException(e);
+ }
+
+ boolean shouldAck = channel != null && !channel.equals(Session.DEFAULT_CHANNEL);
+
+ if (shouldAck) {
+ boolean acked = ackManager.acknowledge(authToken, channel, ackId);
+ if (!acked) {
+ throw new InternalServerErrorException("Ack ID " + ackId + " not Acked.");
+ }
+ }
+
+ ObjectNode responseNode = this.objectMapper.createObjectNode();
+
+ responseNode.put("text", "Success");
+ responseNode.put("code", 0);
+ if (shouldAck) {
+ responseNode.put("ackID", ackId);
+ }
+
+ return responseNode;
+ }
+
+ /*
+ * Pre-handles the event and assigns it's information into HttpEventData object
+ * and returns it. Information from the string is read with ObjectMapper into a
+ * JsonNode. After that the supposed fields are checked from the JsonNode and if
+ * the field is found, information from it will be saved in HttpEventData
+ * object. Finally handleTime() is called to assign correct time information to
+ * the object. When multiple events are sent in one request, the value of the
+ * fields are saved for the following events. The values can be overriden. If
+ * the value is overridden, it will stay as so for the following events if it is
+ * not overridden.
+ */
+ private HttpEventData verifyJsonData(String eventInJson, HttpEventData previousEvent) {
+
+ HttpEventData eventData = new HttpEventData();
+
+ /*
+ * Event field cannot be missing or blank. Throws an exception if this is the
+ * case.
+ */
+ JsonNode jsonObject;
+ try {
+ jsonObject = this.objectMapper.readTree(eventInJson);
+ } catch (IOException e) {
+ jsonObject = null;
+ }
+
+ if (jsonObject != null) {
+ JsonNode event = jsonObject.get("event");
+ if (event != null) {
+ eventData.setEvent(event.toString());
+ } else {
+ throw new EventFieldMissingException();
+ }
+ if (eventData.getEvent().matches("\"\"") || eventData.getEvent() == null) {
+ throw new EventFieldBlankException();
+ }
+ eventData = handleTime(eventData, jsonObject, previousEvent);
+ }
+ return eventData;
+ }
+
+ /*
+ * The time stamp of the event can be given as epoch time in the request.
+ */
+ private HttpEventData handleTime(HttpEventData eventData, JsonNode jsonObject, HttpEventData previousEvent) {
+ JsonNode timeObject = jsonObject.get("time");
+
+ /*
+ * If the time is given as a string rather than as a numeral value, the time is
+ * handled in a same way as it is handled when time is not given in a request.
+ */
+ if (timeObject == null || timeObject.isTextual()) {
+ eventData.setTimeParsed(false);
+ eventData.setTimeSource("generated");
+ if (previousEvent != null) {
+ if (previousEvent.isTimeParsed()) {
+ eventData.setTimeAsLong(previousEvent.getTimeAsLong());
+ eventData.setTime(previousEvent.getTime());
+ eventData.setTimeParsed(true);
+ eventData.setTimeSource("reported");
+ }
+ }
+ /*
+ * If the time is given as epoch seconds with a decimal (example:
+ * 1433188255.253), the decimal point must be removed and time is assigned to
+ * HttpEventData object as a long value. convertTimeToEpochMillis() will check
+ * that correct time format is used.
+ */
+ } else if (timeObject.isDouble()) {
+ eventData.setTimeAsLong(removeDecimal(timeObject.asDouble()));
+ eventData.setTime(String.valueOf(eventData.getTimeAsLong()));
+ eventData.setTimeParsed(true);
+ eventData.setTimeSource("reported");
+ eventData = convertTimeToEpochMillis(eventData);
+ /*
+ * If the time is given in a numeral value, it is assigned to HttpEventData
+ * object as a long value. convertTimeToEpochMillis() will check that correct
+ * time format is used.
+ */
+ } else if (timeObject.canConvertToLong()) {
+ eventData.setTimeAsLong(timeObject.asLong());
+ eventData.setTime(jsonObject.get("time").asText());
+ eventData.setTimeParsed(true);
+ eventData.setTimeSource("reported");
+ eventData = convertTimeToEpochMillis(eventData);
+ } else {
+ eventData.setTimeParsed(false);
+ eventData.setTimeSource("generated");
+ }
+
+ return eventData;
+ }
+
+ /*
+ * Takes a double value as a parameter, removes the decimal point from that
+ * value and returns the number as a long value.
+ */
+ private long removeDecimal(double doubleValue) {
+ BigDecimal doubleValueWithDecimal = BigDecimal.valueOf(doubleValue);
+ String stringValue = doubleValueWithDecimal.toString();
+ String stringValueWithoutDecimal = stringValue.replace(".", "");
+ long longValue = Long.parseLong(stringValueWithoutDecimal);
+
+ return longValue;
+ }
+
+ /*
+ * Converts the given time stamp into epoch milliseconds. Takes a HttpEventData
+ * object as a parameter. Gets the time from the variable set in the
+ * HttpEventData object. If the time value in the object has 13 digits, it means
+ * that time has been already given in epoch milliseconds.
+ */
+ private HttpEventData convertTimeToEpochMillis(HttpEventData eventData) {
+ String timeString = eventData.getTime();
+ if (timeString.length() == 13) {
+ return eventData;
+ } else if (timeString.length() >= 10 && timeString.length() < 13) {
+ eventData.setTimeAsLong(eventData.getTimeAsLong() * (long) Math.pow(10, ((13 - timeString.length()))));
+ } else {
+ eventData.setTimeParsed(false);
+ eventData.setTimeSource("generated");
+ }
+ return eventData;
+ }
+
+ /*
+ * Assigns the metadata (authentication token and channel name) to the
+ * HttpEventData object.
+ */
+ private HttpEventData assignMetaData(HttpEventData eventData, String authToken, String channel) {
+
+ eventData.setAuthenticationToken(authToken);
+ eventData.setChannel(channel);
+
+ return eventData;
+ }
+}
diff --git a/src/main/java/com/teragrep/cfe_16/LifeCycle.java b/src/main/java/com/teragrep/cfe_16/LifeCycle.java
new file mode 100644
index 0000000..dee9d4b
--- /dev/null
+++ b/src/main/java/com/teragrep/cfe_16/LifeCycle.java
@@ -0,0 +1,62 @@
+/*
+ * HTTP Event Capture to RFC5424 CFE_16
+ * Copyright (C) 2021 Suomen Kanuuna Oy
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see .
+ *
+ *
+ * Additional permission under GNU Affero General Public License version 3
+ * section 7
+ *
+ * If you modify this Program, or any covered work, by linking or combining it
+ * with other code, such other code is not for that reason alone subject to any
+ * of the requirements of the GNU Affero GPL version 3 as long as this Program
+ * is the same Program as licensed from Suomen Kanuuna Oy without any additional
+ * modifications.
+ *
+ * Supplemented terms under GNU Affero General Public License version 3
+ * section 7
+ *
+ * Origin of the software must be attributed to Suomen Kanuuna Oy. Any modified
+ * versions must be marked as "Modified version of" The Program.
+ *
+ * Names of the licensors and authors may not be used for publicity purposes.
+ *
+ * No rights are granted for use of trade names, trademarks, or service marks
+ * which are in The Program if any.
+ *
+ * Licensee must indemnify licensors and authors for any liability that these
+ * contractual assumptions impose on licensors and authors.
+ *
+ * To the extent this program is licensed as part of the Commercial versions of
+ * Teragrep, the applicable Commercial License may apply to this file if you as
+ * a licensee so wish it.
+ */
+
+package com.teragrep.cfe_16;
+
+/**
+ * An interface that own resources like threads for doing
+ * graceful shutdown.
+ *
+ */
+public interface LifeCycle {
+
+ public void start();
+
+ /**
+ * This call is supposed to do a graceful cleanup of the implementing class.
+ */
+ public void stop();
+}
diff --git a/src/main/java/com/teragrep/cfe_16/RequestBodyCleaner.java b/src/main/java/com/teragrep/cfe_16/RequestBodyCleaner.java
new file mode 100644
index 0000000..aadf2bc
--- /dev/null
+++ b/src/main/java/com/teragrep/cfe_16/RequestBodyCleaner.java
@@ -0,0 +1,82 @@
+/*
+ * HTTP Event Capture to RFC5424 CFE_16
+ * Copyright (C) 2021 Suomen Kanuuna Oy
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see .
+ *
+ *
+ * Additional permission under GNU Affero General Public License version 3
+ * section 7
+ *
+ * If you modify this Program, or any covered work, by linking or combining it
+ * with other code, such other code is not for that reason alone subject to any
+ * of the requirements of the GNU Affero GPL version 3 as long as this Program
+ * is the same Program as licensed from Suomen Kanuuna Oy without any additional
+ * modifications.
+ *
+ * Supplemented terms under GNU Affero General Public License version 3
+ * section 7
+ *
+ * Origin of the software must be attributed to Suomen Kanuuna Oy. Any modified
+ * versions must be marked as "Modified version of" The Program.
+ *
+ * Names of the licensors and authors may not be used for publicity purposes.
+ *
+ * No rights are granted for use of trade names, trademarks, or service marks
+ * which are in The Program if any.
+ *
+ * Licensee must indemnify licensors and authors for any liability that these
+ * contractual assumptions impose on licensors and authors.
+ *
+ * To the extent this program is licensed as part of the Commercial versions of
+ * Teragrep, the applicable Commercial License may apply to this file if you as
+ * a licensee so wish it.
+ */
+
+package com.teragrep.cfe_16;
+
+import org.springframework.stereotype.Component;
+
+import java.util.regex.Pattern;
+
+/*
+ * Cleans the request body so, that only the body of the request is left in the string.
+ * This is needed when calling the endpoint that consumes MediaType.APPLICATION_FORM_URLENCODED_VALUE
+ * Example of body sent as a parameter: {channel=[CHANNEL_11111], {"sourcetype": "mysourcetype", "event": "Hello, world!"}=[]}
+ * Example of cleaned body returned by the cleanAckRequestBody(): {"sourcetype": "mysourcetype", "event": "Hello, world!"}
+ * TODO: Try to implement a better way to get the body of the request.
+ *
+ */
+@Component
+public class RequestBodyCleaner {
+
+ public String cleanAckRequestBody(String body, String channel) {
+ String bodyWithoutChannel = body.replaceAll("channel=\\[" + Pattern.quote(channel) + "\\]\\, ", "");
+ String bodywithoutChannelLastCharRemoved = removeLastChar(bodyWithoutChannel);
+
+ String cleanedBody = bodywithoutChannelLastCharRemoved.substring(1);
+
+ cleanedBody = removeEqualsArrayFromEnd(cleanedBody);
+
+ return cleanedBody;
+ }
+
+ private String removeLastChar(String str) {
+ return str.substring(0, str.length() - 1);
+ }
+
+ private String removeEqualsArrayFromEnd(String str) {
+ return str.replace("=[]", "");
+ }
+}
diff --git a/src/main/java/com/teragrep/cfe_16/RequestHandler.java b/src/main/java/com/teragrep/cfe_16/RequestHandler.java
new file mode 100644
index 0000000..8b3b337
--- /dev/null
+++ b/src/main/java/com/teragrep/cfe_16/RequestHandler.java
@@ -0,0 +1,90 @@
+/*
+ * HTTP Event Capture to RFC5424 CFE_16
+ * Copyright (C) 2021 Suomen Kanuuna Oy
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see .
+ *
+ *
+ * Additional permission under GNU Affero General Public License version 3
+ * section 7
+ *
+ * If you modify this Program, or any covered work, by linking or combining it
+ * with other code, such other code is not for that reason alone subject to any
+ * of the requirements of the GNU Affero GPL version 3 as long as this Program
+ * is the same Program as licensed from Suomen Kanuuna Oy without any additional
+ * modifications.
+ *
+ * Supplemented terms under GNU Affero General Public License version 3
+ * section 7
+ *
+ * Origin of the software must be attributed to Suomen Kanuuna Oy. Any modified
+ * versions must be marked as "Modified version of" The Program.
+ *
+ * Names of the licensors and authors may not be used for publicity purposes.
+ *
+ * No rights are granted for use of trade names, trademarks, or service marks
+ * which are in The Program if any.
+ *
+ * Licensee must indemnify licensors and authors for any liability that these
+ * contractual assumptions impose on licensors and authors.
+ *
+ * To the extent this program is licensed as part of the Commercial versions of
+ * Teragrep, the applicable Commercial License may apply to this file if you as
+ * a licensee so wish it.
+ */
+
+package com.teragrep.cfe_16;
+
+import com.teragrep.cfe_16.bo.HeaderInfo;
+import javax.servlet.http.HttpServletRequest;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.stereotype.Component;
+
+/**
+ *
+ */
+@Component
+public class RequestHandler {
+ private static final Logger LOGGER = LoggerFactory.getLogger(RequestHandler.class);
+ private String xForwardedFor;
+ private String xForwardedHost;
+ private String xForwardedProto;
+
+ public HeaderInfo createHeaderInfoObject(HttpServletRequest request) {
+ LOGGER.debug("Creating new Header Info");
+ HeaderInfo headerInfo = new HeaderInfo();
+ xForwardedFor = request.getHeader("X-Forwarded-For");
+ xForwardedHost = request.getHeader("X-Forwarded-Host");
+ xForwardedProto = request.getHeader("X-Forwarded-Proto");
+ if (xForwardedFor != null) {
+ LOGGER.debug("Setting X-Forwarded-For");
+ LOGGER.trace("Setting X-Forwarded-For to value <[{}]>", xForwardedFor);
+ headerInfo.setxForwardedFor(xForwardedFor);
+ }
+ if (xForwardedHost != null) {
+ LOGGER.debug("Setting X-Forwarded-Host");
+ LOGGER.trace("Setting X-Forwarded-Host to value <[{}]>", xForwardedHost);
+ headerInfo.setxForwardedHost(xForwardedHost);
+ }
+ if (xForwardedProto != null) {
+ LOGGER.debug("Setting X-Forwarded-Proto");
+ LOGGER.trace("Setting X-Forwarded-Proto to value <[{}]>", xForwardedProto);
+ headerInfo.setxForwardedProto(xForwardedProto);
+ }
+
+ return headerInfo;
+ }
+
+}
diff --git a/src/main/java/com/teragrep/cfe_16/SessionManager.java b/src/main/java/com/teragrep/cfe_16/SessionManager.java
new file mode 100644
index 0000000..94dd2e5
--- /dev/null
+++ b/src/main/java/com/teragrep/cfe_16/SessionManager.java
@@ -0,0 +1,177 @@
+/*
+ * HTTP Event Capture to RFC5424 CFE_16
+ * Copyright (C) 2021 Suomen Kanuuna Oy
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see .
+ *
+ *
+ * Additional permission under GNU Affero General Public License version 3
+ * section 7
+ *
+ * If you modify this Program, or any covered work, by linking or combining it
+ * with other code, such other code is not for that reason alone subject to any
+ * of the requirements of the GNU Affero GPL version 3 as long as this Program
+ * is the same Program as licensed from Suomen Kanuuna Oy without any additional
+ * modifications.
+ *
+ * Supplemented terms under GNU Affero General Public License version 3
+ * section 7
+ *
+ * Origin of the software must be attributed to Suomen Kanuuna Oy. Any modified
+ * versions must be marked as "Modified version of" The Program.
+ *
+ * Names of the licensors and authors may not be used for publicity purposes.
+ *
+ * No rights are granted for use of trade names, trademarks, or service marks
+ * which are in The Program if any.
+ *
+ * Licensee must indemnify licensors and authors for any liability that these
+ * contractual assumptions impose on licensors and authors.
+ *
+ * To the extent this program is licensed as part of the Commercial versions of
+ * Teragrep, the applicable Commercial License may apply to this file if you as
+ * a licensee so wish it.
+ */
+
+package com.teragrep.cfe_16;
+
+import com.teragrep.cfe_16.bo.Session;
+import com.teragrep.cfe_16.config.Configuration;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Component;
+
+import javax.annotation.PostConstruct;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.Map;
+
+/*
+ * Manager that handles creating sessions and getting already existing sessions.
+ * Sessions are indexed by the authentication token.
+ *
+ */
+@Component
+public class SessionManager implements Runnable, LifeCycle {
+ private static final Logger LOGGER = LoggerFactory.getLogger(SessionManager.class);
+ /**
+ * Maps auth token string => session object.
+ */
+ private final Map sessions;
+
+ /**
+ * Cleans up outdated Session objects.
+ */
+ private Thread cleanerThread;
+
+ @Autowired
+ private Configuration configuration;
+
+ /**
+ *
+ */
+ public SessionManager() {
+ this.sessions = new HashMap();
+ }
+
+ @Override
+ @PostConstruct
+ public void start() {
+ this.cleanerThread = new Thread(this, "Session cleaner");
+ this.cleanerThread.start();
+ }
+
+ @Override
+ public void stop() {
+ this.cleanerThread.interrupt();
+ }
+
+ @Override
+ public void run() {
+ while (true) {
+ try {
+ LOGGER.debug("Sleeping for <{}> while waiting for poll", this.configuration.getPollTime());
+ Thread.sleep(this.configuration.getPollTime());
+ } catch (InterruptedException e) {
+ break;
+ }
+ synchronized (this) {
+ Iterator> iterator = this.sessions.entrySet().iterator();
+ while (iterator.hasNext()) {
+ Map.Entry entry = iterator.next();
+ long now = System.currentTimeMillis();
+ long thresholdInLong = entry.getValue().getLastTouchedTimestamp() + this.configuration.getMaxSessionAge();
+ if (now >= thresholdInLong) {
+ iterator.remove();
+ }
+ }
+ }
+ }
+ }
+
+
+
+ /*
+ * Gets a session for provided authentication token. returns null if
+ * there is no session for given authentication token
+ */
+ public Session getSession(String authenticationToken) {
+ synchronized (this) {
+ Session session = this.sessions.get(authenticationToken);
+ return session;
+ }
+ }
+
+ /**
+ * Returns an existing Session object based on authentication token.
+ * If no Session exists, a new one is created.
+ *
+ * @param authenticationToken
+ * @return
+ */
+ public Session getOrCreateSession(String authenticationToken) {
+ LOGGER.debug("Getting or creating session");
+ LOGGER.trace("Getting or creating session for authenticationToken: {}", authenticationToken);
+ synchronized (this) {
+ Session session = this.sessions.get(authenticationToken);
+ if (session == null) {
+ session = new Session(null, authenticationToken);
+ this.sessions.put(authenticationToken, session);
+ }
+ return session;
+ }
+ }
+
+ public void removeSession(String authenticationToken) {
+ LOGGER.debug("Removing session");
+ LOGGER.trace("Removing session for authenticationToken: {}", authenticationToken);
+ synchronized (this) {
+ this.sessions.remove(authenticationToken);
+ }
+ }
+
+ /*
+ * Creates a new session object
+ */
+ public Session createSession(String authenticationToken) {
+ LOGGER.debug("Creating new session");
+ LOGGER.trace("Creating new session for authenticationToken: {}", authenticationToken);
+ synchronized (this) {
+ Session session = new Session(authenticationToken);
+ this.sessions.put(authenticationToken, session);
+ return session;
+ }
+ }
+}
diff --git a/src/main/java/com/teragrep/cfe_16/TestClient.java b/src/main/java/com/teragrep/cfe_16/TestClient.java
new file mode 100644
index 0000000..912932f
--- /dev/null
+++ b/src/main/java/com/teragrep/cfe_16/TestClient.java
@@ -0,0 +1,425 @@
+/*
+ * HTTP Event Capture to RFC5424 CFE_16
+ * Copyright (C) 2021 Suomen Kanuuna Oy
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see .
+ *
+ *
+ * Additional permission under GNU Affero General Public License version 3
+ * section 7
+ *
+ * If you modify this Program, or any covered work, by linking or combining it
+ * with other code, such other code is not for that reason alone subject to any
+ * of the requirements of the GNU Affero GPL version 3 as long as this Program
+ * is the same Program as licensed from Suomen Kanuuna Oy without any additional
+ * modifications.
+ *
+ * Supplemented terms under GNU Affero General Public License version 3
+ * section 7
+ *
+ * Origin of the software must be attributed to Suomen Kanuuna Oy. Any modified
+ * versions must be marked as "Modified version of" The Program.
+ *
+ * Names of the licensors and authors may not be used for publicity purposes.
+ *
+ * No rights are granted for use of trade names, trademarks, or service marks
+ * which are in The Program if any.
+ *
+ * Licensee must indemnify licensors and authors for any liability that these
+ * contractual assumptions impose on licensors and authors.
+ *
+ * To the extent this program is licensed as part of the Commercial versions of
+ * Teragrep, the applicable Commercial License may apply to this file if you as
+ * a licensee so wish it.
+ */
+
+package com.teragrep.cfe_16;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.io.*;
+import java.net.HttpURLConnection;
+import java.net.Socket;
+import java.net.SocketException;
+
+/**
+ * A multithreaded load test client for the cfe_16 server.
+ *
+ */
+public class TestClient implements Runnable {
+ private static final Logger LOGGER = LoggerFactory.getLogger(TestClient.class);
+ /**
+ * How many loops a single thread does.
+ */
+ private int n;
+
+ /**
+ * Hostname or IP address of the cfe_16 server.
+ */
+ private String host;
+
+ /**
+ * TCP port of the cfe_16 server.
+ */
+ private int port;
+
+ public TestClient(int n, String host, int port) throws IOException {
+ this.n = n;
+ this.host = host;
+ this.port = port;
+ LOGGER.info("Initialized TestClient, sending <[{}]> messages to <[{}]>:<[{}]>", n, host, port);
+ }
+
+ public void run() {
+ Socket socket;
+ try {
+ socket = createSocket();
+ } catch (IOException e) {
+ throw new RuntimeException(e);
+ }
+ for (int i = 0; i < this.n; i++) {
+
+ if (i % 10 == 0) {
+ System.out.print('.');
+ }
+
+ socket = testSendEvent(socket);
+ socket = testSendEventWithAckID(socket);
+ socket = testSendEventWithoutAuthorization(socket);
+ socket = testSendEventWithoutEventField(socket);
+ socket = testSendEventWithBlankEventField(socket);
+ socket = testGetAcksWithoutChannel(socket);
+ socket = testGetAcksWithInvalidChannel(socket);
+ }
+ try {
+ socket.close();
+ } catch (IOException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ private Socket testGetAcksWithInvalidChannel(Socket socket) {
+ String path = "/services/collector/ack?channel=CHANNEL_22222";
+ String request = "{\"acks\": [1,3,4]}";
+ String expectedRegex = "\\{\"text\":\"Invalid data channel\",\"code\":11,\"invalid-event-number\":0\\}";
+ String authorization = "AUTH_TOKEN_11111";
+ socket = doRequestAndVerifyReply(socket, path, expectedRegex, request, authorization, HttpURLConnection.HTTP_BAD_REQUEST);
+ return socket;
+ }
+
+ private Socket testGetAcksWithoutChannel(Socket socket) {
+ String path = "/services/collector/ack";
+ String request = "{\"acks\": [1,3,4]}";
+ String expectedRegex = "\\{\"text\":\"Data channel is missing\",\"code\":10,\"invalid-event-number\":0\\}";
+ String authorization = "AUTH_TOKEN_11111";
+ socket = doRequestAndVerifyReply(socket, path, expectedRegex, request, authorization, HttpURLConnection.HTTP_BAD_REQUEST);
+ return socket;
+ }
+
+ private Socket testSendEventWithBlankEventField(Socket socket) {
+ String path = "/services/collector?channel=00872DC6-AC83-4EDE-8AFE-8413C3825C4C";
+ String request = "{\"sourcetype\": \"mysourcetype\", \"event\": \"\"}";
+ String expectedRegex = "\\{\"text\":\"Event field cannot be blank\",\"code\":13,\"invalid-event-number\":0\\}";
+ String authorization = "AUTH_TOKEN_11111";
+ socket = doRequestAndVerifyReply(socket, path, expectedRegex, request, authorization, HttpURLConnection.HTTP_BAD_REQUEST);
+ return socket;
+ }
+
+ private Socket testSendEventWithoutEventField(Socket socket) {
+ String path = "/services/collector?channel=00872DC6-AC83-4EDE-8AFE-8413C3825C4C";
+ String request = "{\"sourcetype\": \"mysourcetype\"}";
+ String expectedRegex = "\\{\"text\":\"Event field is required\",\"code\":12,\"invalid-event-number\":0\\}";
+ String authorization = "AUTH_TOKEN_11111";
+ socket = doRequestAndVerifyReply(socket, path, expectedRegex, request, authorization, HttpURLConnection.HTTP_BAD_REQUEST);
+ return socket;
+ }
+
+ private Socket testSendEventWithoutAuthorization(Socket socket) {
+ String path = "/services/collector?channel=00872DC6-AC83-4EDE-8AFE-8413C3825C4C";
+ String request = "{\"sourcetype\": \"mysourcetype\", \"event\": \"Hello, world!\"}}";
+ String expectedRegex = "\\{\"text\":\"Token is required\",\"code\":2,\"invalid-event-number\":0\\}";
+ socket = doRequestAndVerifyReply(socket, path, expectedRegex, request, null, HttpURLConnection.HTTP_UNAUTHORIZED);
+ return socket;
+ }
+
+ private Socket testSendEventWithAckID(Socket socket) {
+ String path = "/services/collector?channel=CHANNEL_11111";
+ String request = "{\"sourcetype\": \"mysourcetype\", \"event\": \"Hello, world!\"}";
+ String expectedRegex = "\\{\"text\":\"Success\",\"code\":0,\"ackID\":([0-9])+\\}";
+ String authorization = "AUTH_TOKEN_11111";
+ socket = doRequestAndVerifyReply(socket, path, expectedRegex, request, authorization, HttpURLConnection.HTTP_OK);
+ return socket;
+ }
+
+ private Socket testSendEvent(Socket socket) {
+ String path = "/services/collector";
+ String request = "{\"sourcetype\":\"access\", \"source\":\"/var/log/access.log\", \"event\": {\"message\":\"Access log test message\"}} {\"sourcetype\":\"access\", \"source\":\"/var/log/access.log\", \"event\": {\"message\":\"Access log test message 2\"}}";
+ String expectedRegex = "\\{\"text\":\"Success\",\"code\":0\\}";
+ String authorization = "AUTH_TOKEN_11111";
+ socket = doRequestAndVerifyReply(socket, path, expectedRegex, request, authorization, HttpURLConnection.HTTP_OK);
+ return socket;
+ }
+
+ private Socket createSocket() throws IOException {
+ Socket socket = new Socket(this.host, this.port);
+ socket.setKeepAlive(true);
+ socket.setTcpNoDelay(true);
+ return socket;
+ }
+
+ private Socket doRequestAndVerifyReply(Socket socket,
+ String path,
+ String expectedRegex,
+ String request,
+ String authorization,
+ int expectedHttpStatusCode) {
+ while (true) {
+ try {
+ // ensure that the TCP connection is alive
+ socket = ensureTcpConnection(socket);
+
+ // send the http request
+ sendRequest(socket, path, request, authorization);
+
+ BufferedReader bufferedReader = getReader(socket);
+ // read first line of the HTTP response
+ String line;
+ try {
+ line = bufferedReader.readLine();
+ if (line == null) {
+ // retry
+ cleanup(socket, bufferedReader);
+ continue;
+ }
+ } catch (SocketException e) {
+ // server RST'ed connection, retry
+ cleanup(socket, bufferedReader);
+ continue;
+ }
+
+ // parse the status code
+ checkHttpStatusCode(expectedHttpStatusCode, line);
+
+ // skip headers, check for Connection: close
+ // if connection is closed, a new socket is created
+ // but the response can still be read from bufferedReader
+ socket = readHeaders(socket, bufferedReader);
+
+ // read the response
+ String responseBody = readResponseBody(bufferedReader);
+ // check if the response matches to regex
+ validateResponseBody(expectedRegex, responseBody);
+ // we are done
+ break;
+ } catch (SocketException e) {
+ // try again
+ } catch (IOException e) {
+ throw new RuntimeException(e);
+ }
+ }
+ return socket;
+ }
+
+ /**
+ * Validates the response body line against the given
+ * regural expression.
+ *
+ * @param expectedRegex
+ * @param responseBody
+ */
+ private void validateResponseBody(String expectedRegex, String responseBody) {
+ if (!responseBody.matches(expectedRegex)) {
+ throw new RuntimeException(expectedRegex + " did not match to " + responseBody);
+ }
+ }
+
+ /**
+ * Verifies that the HTTP reply status code was what it was
+ * supposed to me.
+ *
+ * @param expectedHttpStatusCode
+ * @param line
+ */
+ private void checkHttpStatusCode(int expectedHttpStatusCode, String line) {
+ String statusCodeText = line.substring(9, 12);
+ int responseCode = Integer.parseInt(statusCodeText);
+ if (responseCode != expectedHttpStatusCode) {
+ throw new RuntimeException("HTTP/" + responseCode + " which was not expected.");
+ }
+ }
+
+ /**
+ * Advances buffered reader through HTTP headers.
+ * If "Connection: close" header is seen, the socket
+ * is closed and a new one is created.
+ *
+ * @param socket
+ * @param bufferedReader
+ * @return
+ * @throws IOException
+ */
+ private Socket readHeaders(Socket socket, BufferedReader bufferedReader) throws IOException {
+ String line;
+ while (true) {
+ line = bufferedReader.readLine();
+ if (line.equals("Connection: close\r\n")) {
+ cleanup(socket, bufferedReader);
+ socket = this.createSocket();
+ break;
+ }
+ if (line.equals("")) {
+ break;
+ }
+ }
+ return socket;
+ }
+
+ /**
+ * Reads the reply line from the servere. Chunked transfer
+ * is assumed.
+ *
+ * @param bufferedReader
+ * @return
+ * @throws IOException
+ */
+ private String readResponseBody(BufferedReader bufferedReader) throws IOException {
+ // skip chunked tag before body
+ bufferedReader.readLine();
+ // read the json response body
+ String responseBody = bufferedReader.readLine();
+ bufferedReader.readLine();
+ return responseBody;
+ }
+
+ private BufferedReader getReader(Socket socket) throws IOException {
+ BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(socket.getInputStream()));
+ return bufferedReader;
+ }
+
+ /**
+ * Checks that the TCP connection is open, otherwis a new
+ * connection is created and returned.
+ *
+ * @param socket
+ * @return
+ * @throws IOException
+ */
+ private Socket ensureTcpConnection(Socket socket) throws IOException {
+ if (socket.isInputShutdown() || socket.isClosed() || !socket.isConnected()) {
+ socket.close();
+ socket = createSocket();
+ }
+ return socket;
+ }
+
+ /**
+ * Does a HTTP request and keeps connection open.
+ *
+ * @param socket
+ * @param path
+ * @param request
+ * @param authorization
+ * @throws IOException
+ */
+ private void sendRequest(Socket socket, String path, String request, String authorization)
+ throws IOException {
+ OutputStream outputStream = socket.getOutputStream();
+ PrintWriter printWriter = new PrintWriter(outputStream);
+ printWriter.write("POST " + path + " HTTP/1.1\r\n");
+ printWriter.write("Connection: keep-alive\r\n");
+ printWriter.write("Content-Type: application/json;charset=UTF-8\r\n");
+ printWriter.write("Accept: application/json\r\n");
+ if (authorization != null) {
+ printWriter.write("Authorization: " + authorization + "\r\n");
+ }
+ printWriter.write("User-Agent: cfe_16 test client v1\r\n");
+ printWriter.write("Host: localhost:" + this.port + "\r\n");
+ printWriter.write("Content-Length: " + request.length() + "\r\n");
+ printWriter.write("\r\n");
+ printWriter.write(request);
+ printWriter.flush();
+ }
+
+ private void cleanup(Socket socket, BufferedReader bufferedReader) throws IOException {
+ bufferedReader.close();
+ socket.close();
+ }
+
+ public static void main(String[] args) throws IOException, InterruptedException {
+ if (args.length >= 4) {
+ String host = args[0];
+ int port = Integer.valueOf(args[1]);
+ LOGGER.info("Connecting to <[{}]>:<[{}]>", host, port);
+ int numberOfThreads = Integer.valueOf(args[2]);
+ int numberOfLoops = Integer.valueOf(args[3]);
+ TestClient[] testClients = createTestClients(host, port, numberOfThreads, numberOfLoops);
+ Thread[] threads = createThreads(numberOfThreads, testClients);
+ long t1 = System.currentTimeMillis();
+ startThreads(numberOfThreads, threads);
+ waitThreadsToFinish(numberOfThreads, threads);
+ long t2 = System.currentTimeMillis();
+ long dt = t2 - t1;
+ int numberOfRequests = numberOfThreads * numberOfLoops;
+ double millisecsPerRequest = (double)dt / (double)numberOfRequests;
+ double throughput = 1000.0 / millisecsPerRequest;
+ LOGGER.info("Did <[{}]> requests in <{}> milliseconds, that is <{}> ms/req., which is <{}> transactions/sec.", numberOfRequests, dt, millisecsPerRequest, throughput);
+ FileOutputStream fileOutputStream = new FileOutputStream("stats.csv", true);
+ BufferedWriter bufferedWriter = new BufferedWriter(new OutputStreamWriter(fileOutputStream));
+ String line = numberOfThreads + "," + millisecsPerRequest + "," + throughput + "\n";
+ bufferedWriter.write(line);
+ bufferedWriter.close();
+ fileOutputStream.close();
+ } else {
+ LOGGER.error("Usage: Usage: ");
+ }
+ }
+
+ private static void waitThreadsToFinish(int numberOfThreads, Thread[] threads) throws InterruptedException {
+ for (int i = 0; i < numberOfThreads; i++) {
+ LOGGER.debug("Waiting for thread <{}> out of <{}> threads to finish", i, numberOfThreads);
+ threads[i].join();
+ LOGGER.debug("Thread <[{}]> of <[{}]> threads finished", i, numberOfThreads);
+ }
+ }
+
+ private static void startThreads(int numberOfThreads, Thread[] threads) {
+ LOGGER.debug("Starting <[{}]> threads", numberOfThreads);
+ for (int i = 0; i < numberOfThreads; i++) {
+ LOGGER.debug("Starting thread <{}> of <{}>", i, numberOfThreads);
+ threads[i].start();
+ }
+ }
+
+ private static Thread[] createThreads(int numberOfThreads, TestClient[] testClients) {
+ LOGGER.debug("Creating <[{}]> threads", numberOfThreads);
+ Thread[] threads = new Thread[numberOfThreads];
+ for (int i = 0; i < numberOfThreads; i++) {
+ LOGGER.debug("Creating thread <{}> of <[{}]>", i, numberOfThreads);
+ threads[i] = new Thread(testClients[i]);
+ }
+ return threads;
+ }
+
+ private static TestClient[] createTestClients(String host, int port, int numberOfThreads, int numberOfLoops)
+ throws IOException {
+ LOGGER.debug("Creating <[{}]> test clients", numberOfThreads);
+ TestClient[] testClients = new TestClient[numberOfThreads];
+ for (int i = 0; i < numberOfThreads; i++) {
+ LOGGER.debug("Creating testClient <{}> of <[{}]>", i, numberOfThreads);
+ testClients[i] = new TestClient(numberOfLoops, host, port);
+ }
+ return testClients;
+ }
+}
diff --git a/src/main/java/com/teragrep/cfe_16/ThirdParty/SyslogMessageSender/SyslogMessageSender.java b/src/main/java/com/teragrep/cfe_16/ThirdParty/SyslogMessageSender/SyslogMessageSender.java
new file mode 100644
index 0000000..44e91a3
--- /dev/null
+++ b/src/main/java/com/teragrep/cfe_16/ThirdParty/SyslogMessageSender/SyslogMessageSender.java
@@ -0,0 +1,289 @@
+/*
+ * Copyright 2010-2014, CloudBees Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.teragrep.cfe_16.ThirdParty.SyslogMessageSender;
+
+import com.cloudbees.syslog.SyslogMessage;
+import com.cloudbees.syslog.sender.AbstractSyslogMessageSender;
+import com.cloudbees.syslog.util.CachingReference;
+import com.cloudbees.syslog.util.IoUtils;
+
+import javax.annotation.Nonnull;
+import javax.annotation.Nullable;
+import javax.annotation.concurrent.ThreadSafe;
+import javax.net.SocketFactory;
+import javax.net.ssl.SSLContext;
+import javax.net.ssl.SSLSession;
+import javax.net.ssl.SSLSocket;
+import javax.net.ssl.SSLSocketFactory;
+import java.io.*;
+import java.math.BigInteger;
+import java.net.*;
+import java.security.cert.Certificate;
+import java.security.cert.X509Certificate;
+import java.util.Objects;
+import java.util.concurrent.atomic.AtomicInteger;
+import java.util.logging.Level;
+
+/**
+ * See RFC 6587 - Transmission of Syslog Messages over TCP
+ *
+ * @author Cyrille Le Clerc
+ */
+@ThreadSafe
+public class SyslogMessageSender extends AbstractSyslogMessageSender implements Closeable {
+ public final static int SETTING_SOCKET_CONNECT_TIMEOUT_IN_MILLIS_DEFAULT_VALUE = 500;
+ public final static int SETTING_MAX_RETRY = 2;
+
+ /**
+ * {@link java.net.InetAddress InetAddress} of the remote Syslog Server.
+ *
+ * The {@code InetAddress} is refreshed regularly to handle DNS changes (default {@link #DEFAULT_INET_ADDRESS_TTL_IN_MILLIS})
+ *
+ * Default value: {@link #DEFAULT_SYSLOG_HOST}
+ */
+ protected CachingReference syslogServerHostnameReference;
+ /**
+ * Listen port of the remote Syslog server.
+ *
+ * Default: {@link #DEFAULT_SYSLOG_PORT}
+ */
+ protected int syslogServerPort = DEFAULT_SYSLOG_PORT;
+
+ private Socket socket;
+ private Writer writer;
+ private int socketConnectTimeoutInMillis = SETTING_SOCKET_CONNECT_TIMEOUT_IN_MILLIS_DEFAULT_VALUE;
+ private boolean ssl;
+ private SSLContext sslContext;
+ /**
+ * Number of retries to send a message before throwing an exception.
+ */
+ private int maxRetryCount = SETTING_MAX_RETRY;
+ /**
+ * Number of exceptions trying to send message.
+ */
+ protected final AtomicInteger trySendErrorCounter = new AtomicInteger();
+
+ // use the CR LF non transparent framing as described in "3.4.2. Non-Transparent-Framing"
+ private String postfix = "\n";
+
+ @Override
+ public synchronized void sendMessage(@Nonnull SyslogMessage message) throws IOException {
+ sendCounter.incrementAndGet();
+ long nanosBefore = System.nanoTime();
+
+ try {
+ Exception lastException = null;
+ for (int i = 0; i <= maxRetryCount; i++) {
+ try {
+ if (logger.isLoggable(Level.FINEST)) {
+ logger.finest("Send syslog message " + message.toSyslogMessage(messageFormat));
+ }
+ ensureSyslogServerConnection();
+ message.toSyslogMessage(messageFormat, writer);
+ writer.write(postfix);
+ writer.flush();
+ return;
+ } catch (IOException e) {
+ lastException = e;
+ IoUtils.closeQuietly(socket, writer);
+ trySendErrorCounter.incrementAndGet();
+ } catch (RuntimeException e) {
+ lastException = e;
+ IoUtils.closeQuietly(socket, writer);
+ trySendErrorCounter.incrementAndGet();
+ }
+ }
+ if (lastException != null) {
+ sendErrorCounter.incrementAndGet();
+ if (lastException instanceof IOException) {
+ throw (IOException) lastException;
+ } else if (lastException instanceof RuntimeException) {
+ throw (RuntimeException) lastException;
+ }
+ }
+ } finally {
+ sendDurationInNanosCounter.addAndGet(System.nanoTime() - nanosBefore);
+ }
+ }
+
+ private synchronized void ensureSyslogServerConnection() throws IOException {
+ InetAddress inetAddress = syslogServerHostnameReference.get();
+ if (socket != null && !Objects.equals(socket.getInetAddress(), inetAddress)) {
+ logger.info("InetAddress of the Syslog Server have changed, create a new connection. " +
+ "Before=" + socket.getInetAddress() + ", new=" + inetAddress);
+ IoUtils.closeQuietly(socket, writer);
+ writer = null;
+ socket = null;
+ }
+ boolean socketIsValid;
+ try {
+ socketIsValid = socket != null &&
+ socket.isConnected()
+ && socket.isBound()
+ && !socket.isClosed()
+ && !socket.isInputShutdown()
+ && !socket.isOutputShutdown();
+ } catch (Exception e) {
+ socketIsValid = false;
+ }
+ if (!socketIsValid) {
+ writer = null;
+ try {
+ if (ssl) {
+ if (sslContext == null) {
+ socket = SSLSocketFactory.getDefault().createSocket();
+ } else {
+ socket = sslContext.getSocketFactory().createSocket();
+ }
+ } else {
+ socket = SocketFactory.getDefault().createSocket();
+ }
+ socket.setKeepAlive(true);
+ socket.connect(
+ new InetSocketAddress(inetAddress, syslogServerPort),
+ socketConnectTimeoutInMillis);
+
+ if (socket instanceof SSLSocket && logger.isLoggable(Level.FINER)) {
+ try {
+ SSLSocket sslSocket = (SSLSocket) socket;
+ SSLSession session = sslSocket.getSession();
+ logger.finer("The Certificates used by peer");
+ for (Certificate certificate : session.getPeerCertificates()) {
+ if (certificate instanceof X509Certificate) {
+ X509Certificate x509Certificate = (X509Certificate) certificate;
+ logger.finer("" + x509Certificate.getSubjectDN());
+ } else {
+ logger.finer("" + certificate);
+ }
+ }
+ logger.finer("Peer host is " + session.getPeerHost());
+ logger.finer("Cipher is " + session.getCipherSuite());
+ logger.finer("Protocol is " + session.getProtocol());
+ logger.finer("ID is " + new BigInteger(session.getId()));
+ logger.finer("Session created in " + session.getCreationTime());
+ logger.finer("Session accessed in " + session.getLastAccessedTime());
+ } catch (Exception e) {
+ logger.warn("Exception dumping debug info for " + socket, e);
+ }
+ }
+ } catch (IOException e) {
+ ConnectException ce = new ConnectException("Exception connecting to " + inetAddress + ":" + syslogServerPort);
+ ce.initCause(e);
+ throw ce;
+ }
+ }
+ if (writer == null) {
+ writer = new BufferedWriter(new OutputStreamWriter(socket.getOutputStream(), UTF_8));
+ }
+ }
+
+ @Override
+ public void setSyslogServerHostname(final String syslogServerHostname) {
+ this.syslogServerHostnameReference = new CachingReference(DEFAULT_INET_ADDRESS_TTL_IN_NANOS) {
+ @Nullable
+ @Override
+ protected InetAddress newObject() {
+ try {
+ return InetAddress.getByName(syslogServerHostname);
+ } catch (UnknownHostException e) {
+ throw new IllegalStateException(e);
+ }
+ }
+ };
+ }
+
+ @Override
+ public void setSyslogServerPort(int syslogServerPort) {
+ this.syslogServerPort = syslogServerPort;
+ }
+
+ @Nullable
+ public String getSyslogServerHostname() {
+ if (syslogServerHostnameReference == null)
+ return null;
+ InetAddress inetAddress = syslogServerHostnameReference.get();
+ return inetAddress == null ? null : inetAddress.getHostName();
+ }
+
+ public int getSyslogServerPort() {
+ return syslogServerPort;
+ }
+
+ public boolean isSsl() {
+ return ssl;
+ }
+
+ public void setSsl(boolean ssl) {
+ this.ssl = ssl;
+ }
+
+ public synchronized void setSSLContext(SSLContext sslContext) {
+ this.sslContext = sslContext;
+ }
+
+ public synchronized SSLContext getSSLContext() {
+ return this.sslContext;
+ }
+
+ public int getSocketConnectTimeoutInMillis() {
+ return socketConnectTimeoutInMillis;
+ }
+
+ public int getMaxRetryCount() {
+ return maxRetryCount;
+ }
+
+ public int getTrySendErrorCounter() {
+ return trySendErrorCounter.get();
+ }
+
+ public void setSocketConnectTimeoutInMillis(int socketConnectTimeoutInMillis) {
+ this.socketConnectTimeoutInMillis = socketConnectTimeoutInMillis;
+ }
+
+ public void setMaxRetryCount(int maxRetryCount) {
+ this.maxRetryCount = maxRetryCount;
+ }
+
+ public synchronized void setPostfix(String postfix) {
+ this.postfix = postfix;
+ }
+
+ @Override
+ public String toString() {
+ return getClass().getName() + "{" +
+ "syslogServerHostname='" + this.getSyslogServerHostname() + '\'' +
+ ", syslogServerPort='" + this.getSyslogServerPort() + '\'' +
+ ", ssl=" + ssl +
+ ", maxRetryCount=" + maxRetryCount +
+ ", socketConnectTimeoutInMillis=" + socketConnectTimeoutInMillis +
+ ", defaultAppName='" + defaultAppName + '\'' +
+ ", defaultFacility=" + defaultFacility +
+ ", defaultMessageHostname='" + defaultMessageHostname + '\'' +
+ ", defaultSeverity=" + defaultSeverity +
+ ", messageFormat=" + messageFormat +
+ ", sendCounter=" + sendCounter +
+ ", sendDurationInNanosCounter=" + sendDurationInNanosCounter +
+ ", sendErrorCounter=" + sendErrorCounter +
+ ", trySendErrorCounter=" + trySendErrorCounter +
+ '}';
+ }
+
+ @Override
+ public void close() throws IOException {
+ this.socket.close();
+ }
+}
diff --git a/src/main/java/com/teragrep/cfe_16/TokenManager.java b/src/main/java/com/teragrep/cfe_16/TokenManager.java
new file mode 100644
index 0000000..2af9bc3
--- /dev/null
+++ b/src/main/java/com/teragrep/cfe_16/TokenManager.java
@@ -0,0 +1,119 @@
+/*
+ * HTTP Event Capture to RFC5424 CFE_16
+ * Copyright (C) 2021 Suomen Kanuuna Oy
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see .
+ *
+ *
+ * Additional permission under GNU Affero General Public License version 3
+ * section 7
+ *
+ * If you modify this Program, or any covered work, by linking or combining it
+ * with other code, such other code is not for that reason alone subject to any
+ * of the requirements of the GNU Affero GPL version 3 as long as this Program
+ * is the same Program as licensed from Suomen Kanuuna Oy without any additional
+ * modifications.
+ *
+ * Supplemented terms under GNU Affero General Public License version 3
+ * section 7
+ *
+ * Origin of the software must be attributed to Suomen Kanuuna Oy. Any modified
+ * versions must be marked as "Modified version of" The Program.
+ *
+ * Names of the licensors and authors may not be used for publicity purposes.
+ *
+ * No rights are granted for use of trade names, trademarks, or service marks
+ * which are in The Program if any.
+ *
+ * Licensee must indemnify licensors and authors for any liability that these
+ * contractual assumptions impose on licensors and authors.
+ *
+ * To the extent this program is licensed as part of the Commercial versions of
+ * Teragrep, the applicable Commercial License may apply to this file if you as
+ * a licensee so wish it.
+ */
+
+package com.teragrep.cfe_16;
+
+import javax.servlet.http.HttpServletRequest;
+import org.springframework.stereotype.Component;
+
+import java.nio.charset.StandardCharsets;
+import java.util.Base64;
+
+/*
+ * Manager that handles the authentication token
+ *
+ */
+@Component
+public class TokenManager {
+
+ public TokenManager() {
+ }
+
+ /*
+ * Checks if the authentication token is in the request. Returns true if the
+ * authentication token is NOT in the request. Returns false if authentication
+ * token is found.
+ */
+ // @LogAnnotation(type = LogType.DEBUG)
+ public boolean tokenIsMissing(HttpServletRequest request) {
+
+ // AspectLoggerWrapper.log(new PayloadWrapper(OffsetDateTime.MAX, 4,
+ // LogValues.LogClass.audit, LogValues.GdprData.identification, "application",
+ // "environment",
+ // "component", "instance", LogValues.RetentionTime.P1Y,
+ // UUID.randomUUID().toString(), null));
+ String authHeader = request.getHeader("Authorization");
+ return (authHeader == null || authHeader.isEmpty());
+ }
+
+ /*
+ * Checks if the authentication token is given in basic authentication. Returns
+ * true if the token is in basic and false if not. Authorization header is given
+ * as a string parameter.
+ */
+ // @LogAnnotation(type = LogType.DEBUG)
+ public boolean isTokenInBasic(String authHeader) {
+
+ boolean isInBasic = false;
+
+ if (authHeader != null && authHeader.toLowerCase().startsWith("basic")) {
+ isInBasic = true;
+ }
+
+ return isInBasic;
+ }
+
+ /*
+ * Trims the authorization header to get the authentication token from basic
+ * authentication form Authorization header is given as a string parameter.
+ * First the word "Basic" is trimmed out of the string. After that the
+ * credentials are decoded from Base64 and they are saved in a string that
+ * consists of username and a password. Here password is the authenication
+ * token. Username and password is separated into an array. Then we return the
+ * password (authentication token).
+ */
+ // @LogAnnotation(type = LogType.DEBUG)
+ public String getTokenFromBasic(String authHeader) {
+ String base64Credentials = authHeader.substring("Basic".length()).trim();
+ byte[] credDecoded = Base64.getDecoder().decode(base64Credentials);
+ String credentials = new String(credDecoded, StandardCharsets.UTF_8);
+
+ // credentials = username:password
+ final String[] values = credentials.split(":", 2);
+
+ return values[1];
+ }
+}
diff --git a/src/main/java/com/teragrep/cfe_16/bo/Ack.java b/src/main/java/com/teragrep/cfe_16/bo/Ack.java
new file mode 100644
index 0000000..e22c55d
--- /dev/null
+++ b/src/main/java/com/teragrep/cfe_16/bo/Ack.java
@@ -0,0 +1,115 @@
+/*
+ * HTTP Event Capture to RFC5424 CFE_16
+ * Copyright (C) 2021 Suomen Kanuuna Oy
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see .
+ *
+ *
+ * Additional permission under GNU Affero General Public License version 3
+ * section 7
+ *
+ * If you modify this Program, or any covered work, by linking or combining it
+ * with other code, such other code is not for that reason alone subject to any
+ * of the requirements of the GNU Affero GPL version 3 as long as this Program
+ * is the same Program as licensed from Suomen Kanuuna Oy without any additional
+ * modifications.
+ *
+ * Supplemented terms under GNU Affero General Public License version 3
+ * section 7
+ *
+ * Origin of the software must be attributed to Suomen Kanuuna Oy. Any modified
+ * versions must be marked as "Modified version of" The Program.
+ *
+ * Names of the licensors and authors may not be used for publicity purposes.
+ *
+ * No rights are granted for use of trade names, trademarks, or service marks
+ * which are in The Program if any.
+ *
+ * Licensee must indemnify licensors and authors for any liability that these
+ * contractual assumptions impose on licensors and authors.
+ *
+ * To the extent this program is licensed as part of the Commercial versions of
+ * Teragrep, the applicable Commercial License may apply to this file if you as
+ * a licensee so wish it.
+ */
+
+package com.teragrep.cfe_16.bo;
+
+/*
+ * Saves the status of the sent events.
+ * If the Ack is acknowledged, the event is sent succesfully.
+ *
+ */
+public class Ack {
+
+ private int id;
+ private boolean acknowledged;
+ private long lastUsedTimestampInMilliseconds;
+
+ public Ack() {
+ this(0, false);
+ }
+
+ public Ack(int id, boolean acknowledged) {
+ this.id = id;
+ this.acknowledged = acknowledged;
+ this.lastUsedTimestampInMilliseconds = System.currentTimeMillis();
+ }
+
+ public int getId() {
+ return id;
+ }
+
+ public void setId(int id) {
+ this.id = id;
+ }
+
+ public boolean isAcknowledged() {
+ return acknowledged;
+ }
+
+ public void acknowledge() {
+ this.acknowledged = true;
+ }
+
+ @Override
+ public String toString() {
+ return "Ack [id=" + id + ", acknowledged=" + acknowledged + "]";
+ }
+
+ @Override
+ public int hashCode() {
+ final int prime = 31;
+ int result = 1;
+ result = prime * result + this.id;
+ return result;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) {
+ return true;
+ }
+ Ack ack = (Ack) o;
+ return id == ack.id;
+ }
+
+ public void touch() {
+ this.lastUsedTimestampInMilliseconds = System.currentTimeMillis();
+ }
+
+ public long getLastUsedTimestamp() {
+ return this.lastUsedTimestampInMilliseconds;
+ }
+}
diff --git a/src/main/java/com/teragrep/cfe_16/bo/HeaderInfo.java b/src/main/java/com/teragrep/cfe_16/bo/HeaderInfo.java
new file mode 100644
index 0000000..3493572
--- /dev/null
+++ b/src/main/java/com/teragrep/cfe_16/bo/HeaderInfo.java
@@ -0,0 +1,89 @@
+/*
+ * HTTP Event Capture to RFC5424 CFE_16
+ * Copyright (C) 2021 Suomen Kanuuna Oy
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see .
+ *
+ *
+ * Additional permission under GNU Affero General Public License version 3
+ * section 7
+ *
+ * If you modify this Program, or any covered work, by linking or combining it
+ * with other code, such other code is not for that reason alone subject to any
+ * of the requirements of the GNU Affero GPL version 3 as long as this Program
+ * is the same Program as licensed from Suomen Kanuuna Oy without any additional
+ * modifications.
+ *
+ * Supplemented terms under GNU Affero General Public License version 3
+ * section 7
+ *
+ * Origin of the software must be attributed to Suomen Kanuuna Oy. Any modified
+ * versions must be marked as "Modified version of" The Program.
+ *
+ * Names of the licensors and authors may not be used for publicity purposes.
+ *
+ * No rights are granted for use of trade names, trademarks, or service marks
+ * which are in The Program if any.
+ *
+ * Licensee must indemnify licensors and authors for any liability that these
+ * contractual assumptions impose on licensors and authors.
+ *
+ * To the extent this program is licensed as part of the Commercial versions of
+ * Teragrep, the applicable Commercial License may apply to this file if you as
+ * a licensee so wish it.
+ */
+
+package com.teragrep.cfe_16.bo;
+
+/**
+ */
+public class HeaderInfo {
+
+ private String xForwardedFor;
+ private String xForwardedHost;
+ private String xForwardedProto;
+
+ public HeaderInfo() {
+ }
+
+ public HeaderInfo(String xForwardedFor, String xForwardedHost, String xForwardedProto) {
+ this.xForwardedFor = xForwardedFor;
+ this.xForwardedHost = xForwardedHost;
+ this.xForwardedProto = xForwardedProto;
+ }
+
+ public String getxForwardedFor() {
+ return xForwardedFor;
+ }
+
+ public void setxForwardedFor(String xForwardedFor) {
+ this.xForwardedFor = xForwardedFor;
+ }
+
+ public String getxForwardedHost() {
+ return xForwardedHost;
+ }
+
+ public void setxForwardedHost(String xForwardedHost) {
+ this.xForwardedHost = xForwardedHost;
+ }
+
+ public String getxForwardedProto() {
+ return xForwardedProto;
+ }
+
+ public void setxForwardedProto(String xForwardedProto) {
+ this.xForwardedProto = xForwardedProto;
+ }
+}
diff --git a/src/main/java/com/teragrep/cfe_16/bo/HttpEventData.java b/src/main/java/com/teragrep/cfe_16/bo/HttpEventData.java
new file mode 100644
index 0000000..6eabf58
--- /dev/null
+++ b/src/main/java/com/teragrep/cfe_16/bo/HttpEventData.java
@@ -0,0 +1,132 @@
+/*
+ * HTTP Event Capture to RFC5424 CFE_16
+ * Copyright (C) 2021 Suomen Kanuuna Oy
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see .
+ *
+ *
+ * Additional permission under GNU Affero General Public License version 3
+ * section 7
+ *
+ * If you modify this Program, or any covered work, by linking or combining it
+ * with other code, such other code is not for that reason alone subject to any
+ * of the requirements of the GNU Affero GPL version 3 as long as this Program
+ * is the same Program as licensed from Suomen Kanuuna Oy without any additional
+ * modifications.
+ *
+ * Supplemented terms under GNU Affero General Public License version 3
+ * section 7
+ *
+ * Origin of the software must be attributed to Suomen Kanuuna Oy. Any modified
+ * versions must be marked as "Modified version of" The Program.
+ *
+ * Names of the licensors and authors may not be used for publicity purposes.
+ *
+ * No rights are granted for use of trade names, trademarks, or service marks
+ * which are in The Program if any.
+ *
+ * Licensee must indemnify licensors and authors for any liability that these
+ * contractual assumptions impose on licensors and authors.
+ *
+ * To the extent this program is licensed as part of the Commercial versions of
+ * Teragrep, the applicable Commercial License may apply to this file if you as
+ * a licensee so wish it.
+ */
+
+package com.teragrep.cfe_16.bo;
+
+/**
+ */
+public class HttpEventData {
+
+ private String channel;
+ private String event;
+ private String authenticationToken;
+ private String timeSource;
+ private String time;
+ private long timeAsLong;
+ private boolean timeParsed;
+ private Integer ackID;
+
+ public String getEvent() {
+ return event;
+ }
+
+ public void setEvent(String event) {
+ this.event = event;
+ }
+
+ public String getChannel() {
+ return channel;
+ }
+
+ public void setChannel(String channel) {
+ this.channel = channel;
+ }
+
+ public String getAuthenticationToken() {
+ return authenticationToken;
+ }
+
+ public void setAuthenticationToken(String authenticationToken) {
+ this.authenticationToken = authenticationToken;
+ }
+
+ public String getTimeSource() {
+ return timeSource;
+ }
+
+ public void setTimeSource(String timeSource) {
+ this.timeSource = timeSource;
+ }
+
+ public String getTime() {
+ return time;
+ }
+
+ public long getTimeAsLong() {
+ return timeAsLong;
+ }
+
+ public void setTimeAsLong(long timeAsLong) {
+ this.timeAsLong = timeAsLong;
+ }
+
+ public void setTime(String time) {
+ this.time = time;
+ }
+
+ public boolean isTimeParsed() {
+ return timeParsed;
+ }
+
+ public void setTimeParsed(boolean timeParsed) {
+ this.timeParsed = timeParsed;
+ }
+
+ public Integer getAckID() {
+ return ackID;
+ }
+
+ public void setAckID(Integer ackID) {
+ this.ackID = ackID;
+ }
+
+ @Override
+ public String toString() {
+ return "HttpEventData [channel=" + channel + ", event=" + event + ", authenticationToken=" + authenticationToken
+ + ", timeSource=" + timeSource + ", time=" + time + ", timeAsLong=" + timeAsLong + ", timeParsed="
+ + timeParsed + ", ackID=" + ackID + "]";
+ }
+}
diff --git a/src/main/java/com/teragrep/cfe_16/bo/Session.java b/src/main/java/com/teragrep/cfe_16/bo/Session.java
new file mode 100644
index 0000000..8b2fdaa
--- /dev/null
+++ b/src/main/java/com/teragrep/cfe_16/bo/Session.java
@@ -0,0 +1,147 @@
+/*
+ * HTTP Event Capture to RFC5424 CFE_16
+ * Copyright (C) 2021 Suomen Kanuuna Oy
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see .
+ *
+ *
+ * Additional permission under GNU Affero General Public License version 3
+ * section 7
+ *
+ * If you modify this Program, or any covered work, by linking or combining it
+ * with other code, such other code is not for that reason alone subject to any
+ * of the requirements of the GNU Affero GPL version 3 as long as this Program
+ * is the same Program as licensed from Suomen Kanuuna Oy without any additional
+ * modifications.
+ *
+ * Supplemented terms under GNU Affero General Public License version 3
+ * section 7
+ *
+ * Origin of the software must be attributed to Suomen Kanuuna Oy. Any modified
+ * versions must be marked as "Modified version of" The Program.
+ *
+ * Names of the licensors and authors may not be used for publicity purposes.
+ *
+ * No rights are granted for use of trade names, trademarks, or service marks
+ * which are in The Program if any.
+ *
+ * Licensee must indemnify licensors and authors for any liability that these
+ * contractual assumptions impose on licensors and authors.
+ *
+ * To the extent this program is licensed as part of the Commercial versions of
+ * Teragrep, the applicable Commercial License may apply to this file if you as
+ * a licensee so wish it.
+ */
+
+package com.teragrep.cfe_16.bo;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.Set;
+
+
+/**
+ * A Session keeps track of channels that are contained
+ * inside one Session. This class is not thread-safe.
+ *
+ */
+public class Session {
+
+ public static final String DEFAULT_CHANNEL = "defaultchannel";
+ private static final Logger LOGGER = LoggerFactory.getLogger(Session.class);
+ /**
+ * Channels of this Session object.
+ */
+ private Set channels;
+
+ /**
+ * Authentication key of this Session.
+ */
+ private String authenticationToken;
+
+ private long lastTouchedTimestamp;
+
+ @SuppressWarnings("unchecked")
+ public Session(String channel, String authenticationToken) {
+ LOGGER.info("Creating new session with channel <{}>", channel);
+ this.channels = Collections.synchronizedSet(new HashSet());
+ if (channel != null) {
+ LOGGER.info("Adding channel <[{}]>", channel);
+ this.channels.add(channel);
+ }
+ this.authenticationToken = authenticationToken;
+ this.lastTouchedTimestamp = System.currentTimeMillis();
+ }
+
+ public Session(String authenticationToken) {
+ this(null, authenticationToken);
+ }
+
+ public String getAuthenticationToken() {
+ return authenticationToken;
+ }
+
+ public boolean addChannel(String channel) {
+ return this.channels.add(channel);
+ }
+
+ public boolean doesChannelExist(String channel) {
+ return this.channels.contains(channel);
+ }
+
+ public boolean removeChannel(String channel) {
+ return this.channels.remove(channel);
+ }
+
+ @Override
+ public int hashCode() {
+ final int prime = 31;
+ int result = 1;
+ result = prime * result + ((this.authenticationToken == null) ? 0 : this.authenticationToken.hashCode());
+ return result;
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (this == obj)
+ return true;
+ if (obj == null)
+ return false;
+ if (getClass() != obj.getClass())
+ return false;
+ Session other = (Session) obj;
+ if (this.authenticationToken == null) {
+ if (other.authenticationToken != null)
+ return false;
+ } else if (!this.authenticationToken.equals(other.authenticationToken))
+ return false;
+ return true;
+ }
+
+ @Override
+ public String toString() {
+ return "channels=" + this.channels + ", authenticationToken=" + this.authenticationToken + "]";
+ }
+
+ public void touch() {
+ this.lastTouchedTimestamp = System.currentTimeMillis();
+ }
+
+ public long getLastTouchedTimestamp() {
+ return this.lastTouchedTimestamp;
+ }
+}
diff --git a/src/main/java/com/teragrep/cfe_16/config/Configuration.java b/src/main/java/com/teragrep/cfe_16/config/Configuration.java
new file mode 100644
index 0000000..075fb8e
--- /dev/null
+++ b/src/main/java/com/teragrep/cfe_16/config/Configuration.java
@@ -0,0 +1,151 @@
+/*
+ * HTTP Event Capture to RFC5424 CFE_16
+ * Copyright (C) 2021 Suomen Kanuuna Oy
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see .
+ *
+ *
+ * Additional permission under GNU Affero General Public License version 3
+ * section 7
+ *
+ * If you modify this Program, or any covered work, by linking or combining it
+ * with other code, such other code is not for that reason alone subject to any
+ * of the requirements of the GNU Affero GPL version 3 as long as this Program
+ * is the same Program as licensed from Suomen Kanuuna Oy without any additional
+ * modifications.
+ *
+ * Supplemented terms under GNU Affero General Public License version 3
+ * section 7
+ *
+ * Origin of the software must be attributed to Suomen Kanuuna Oy. Any modified
+ * versions must be marked as "Modified version of" The Program.
+ *
+ * Names of the licensors and authors may not be used for publicity purposes.
+ *
+ * No rights are granted for use of trade names, trademarks, or service marks
+ * which are in The Program if any.
+ *
+ * Licensee must indemnify licensors and authors for any liability that these
+ * contractual assumptions impose on licensors and authors.
+ *
+ * To the extent this program is licensed as part of the Commercial versions of
+ * Teragrep, the applicable Commercial License may apply to this file if you as
+ * a licensee so wish it.
+ */
+
+package com.teragrep.cfe_16.config;
+
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.stereotype.Component;
+
+/**
+ * A Spring-utilizing class for getting configuration data.
+ *
+ */
+
+@Component
+public class Configuration {
+
+ @Value("${syslog.server.host}")
+ private String sysLogHost;
+
+ @Value("${syslog.server.protocol}")
+ private String sysLogProtocol;
+
+ @Value("${syslog.server.port}")
+ private int sysLogPort;
+
+ @Value("${max.ack.value}")
+ private int maxAckValue;
+
+ @Value("${max.ack.age}")
+ private int maxAckAge;
+
+ @Value("${max.session.age}")
+ private int maxSessionAge;
+
+ @Value("${max.channels}")
+ private int maxChannels;
+
+ @Value("${max.ack.value}")
+ private long pollTime;
+
+ @Value("${server.print.times}")
+ private boolean printTimes;
+
+ public Configuration() {
+
+ }
+
+ public String getSyslogHost() {
+ return this.sysLogHost;
+ }
+
+ public String getSysLogProtocol() {
+ return this.sysLogProtocol;
+ }
+
+ public void setSysLogProtocol(String sysLogProtocol) {
+ this.sysLogProtocol = sysLogProtocol;
+ }
+
+ public void setSyslogHost(String syslogHost) {
+ this.sysLogHost = syslogHost;
+ }
+
+ public int getSyslogPort() {
+ return this.sysLogPort;
+ }
+
+ public void setSyslogPort(int syslogPort) {
+ this.sysLogPort = syslogPort;
+ }
+
+ public int getMaxAckValue() {
+ return this.maxAckValue;
+ }
+
+ public void setMaxAckValue(int maxAckValue) {
+ this.maxAckValue = maxAckValue;
+ }
+
+ public int getMaxAckAge() {
+ return this.maxAckAge;
+ }
+
+ public int getMaxChannels() {
+ return this.maxChannels;
+ }
+
+ public long getPollTime() {
+ return this.pollTime;
+ }
+
+ public boolean getPrintTimes() {
+ return this.printTimes;
+ }
+
+ public int getMaxSessionAge() {
+ return this.maxSessionAge;
+ }
+
+ @Override
+ public String toString() {
+ return "Configuration [sysLogHost=" + this.sysLogHost + ", sysLogProtocol=" + this.sysLogProtocol
+ + ", sysLogPort=" + this.sysLogPort + ", maxAckValue=" + this.maxAckValue + ", maxAckAge="
+ + this.maxAckAge + ", maxSessionAge=" + this.maxSessionAge + ", maxChannels=" + this.maxChannels
+ + ", pollTime=" + this.pollTime + ", printTimes=" + this.printTimes + "]";
+ }
+
+}
diff --git a/src/main/java/com/teragrep/cfe_16/exceptionhandling/AuthenticationTokenMissingException.java b/src/main/java/com/teragrep/cfe_16/exceptionhandling/AuthenticationTokenMissingException.java
new file mode 100644
index 0000000..b3e70b4
--- /dev/null
+++ b/src/main/java/com/teragrep/cfe_16/exceptionhandling/AuthenticationTokenMissingException.java
@@ -0,0 +1,73 @@
+/*
+ * HTTP Event Capture to RFC5424 CFE_16
+ * Copyright (C) 2021 Suomen Kanuuna Oy
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see .
+ *
+ *
+ * Additional permission under GNU Affero General Public License version 3
+ * section 7
+ *
+ * If you modify this Program, or any covered work, by linking or combining it
+ * with other code, such other code is not for that reason alone subject to any
+ * of the requirements of the GNU Affero GPL version 3 as long as this Program
+ * is the same Program as licensed from Suomen Kanuuna Oy without any additional
+ * modifications.
+ *
+ * Supplemented terms under GNU Affero General Public License version 3
+ * section 7
+ *
+ * Origin of the software must be attributed to Suomen Kanuuna Oy. Any modified
+ * versions must be marked as "Modified version of" The Program.
+ *
+ * Names of the licensors and authors may not be used for publicity purposes.
+ *
+ * No rights are granted for use of trade names, trademarks, or service marks
+ * which are in The Program if any.
+ *
+ * Licensee must indemnify licensors and authors for any liability that these
+ * contractual assumptions impose on licensors and authors.
+ *
+ * To the extent this program is licensed as part of the Commercial versions of
+ * Teragrep, the applicable Commercial License may apply to this file if you as
+ * a licensee so wish it.
+ */
+
+package com.teragrep.cfe_16.exceptionhandling;
+
+@SuppressWarnings("serial")
+public class AuthenticationTokenMissingException extends RuntimeException {
+
+ public AuthenticationTokenMissingException() {
+
+ }
+
+ public AuthenticationTokenMissingException(String message) {
+ super(message);
+ }
+
+ public AuthenticationTokenMissingException(Throwable cause) {
+ super(cause);
+ }
+
+ public AuthenticationTokenMissingException(String message, Throwable cause) {
+ super(message, cause);
+ }
+
+ public AuthenticationTokenMissingException(String message, Throwable cause, boolean enableSuppression,
+ boolean writableStackTrace) {
+ super(message, cause, enableSuppression, writableStackTrace);
+ }
+
+}
diff --git a/src/main/java/com/teragrep/cfe_16/exceptionhandling/ChannelNotFoundException.java b/src/main/java/com/teragrep/cfe_16/exceptionhandling/ChannelNotFoundException.java
new file mode 100644
index 0000000..d4d3206
--- /dev/null
+++ b/src/main/java/com/teragrep/cfe_16/exceptionhandling/ChannelNotFoundException.java
@@ -0,0 +1,73 @@
+/*
+ * HTTP Event Capture to RFC5424 CFE_16
+ * Copyright (C) 2021 Suomen Kanuuna Oy
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see .
+ *
+ *
+ * Additional permission under GNU Affero General Public License version 3
+ * section 7
+ *
+ * If you modify this Program, or any covered work, by linking or combining it
+ * with other code, such other code is not for that reason alone subject to any
+ * of the requirements of the GNU Affero GPL version 3 as long as this Program
+ * is the same Program as licensed from Suomen Kanuuna Oy without any additional
+ * modifications.
+ *
+ * Supplemented terms under GNU Affero General Public License version 3
+ * section 7
+ *
+ * Origin of the software must be attributed to Suomen Kanuuna Oy. Any modified
+ * versions must be marked as "Modified version of" The Program.
+ *
+ * Names of the licensors and authors may not be used for publicity purposes.
+ *
+ * No rights are granted for use of trade names, trademarks, or service marks
+ * which are in The Program if any.
+ *
+ * Licensee must indemnify licensors and authors for any liability that these
+ * contractual assumptions impose on licensors and authors.
+ *
+ * To the extent this program is licensed as part of the Commercial versions of
+ * Teragrep, the applicable Commercial License may apply to this file if you as
+ * a licensee so wish it.
+ */
+
+package com.teragrep.cfe_16.exceptionhandling;
+
+@SuppressWarnings("serial")
+public class ChannelNotFoundException extends RuntimeException {
+
+ public ChannelNotFoundException() {
+ super();
+ }
+
+ public ChannelNotFoundException(String message, Throwable cause, boolean enableSuppression,
+ boolean writableStackTrace) {
+ super(message, cause, enableSuppression, writableStackTrace);
+ }
+
+ public ChannelNotFoundException(String message, Throwable cause) {
+ super(message, cause);
+ }
+
+ public ChannelNotFoundException(String message) {
+ super(message);
+ }
+
+ public ChannelNotFoundException(Throwable cause) {
+ super(cause);
+ }
+
+}
diff --git a/src/main/java/com/teragrep/cfe_16/exceptionhandling/ChannelNotProvidedException.java b/src/main/java/com/teragrep/cfe_16/exceptionhandling/ChannelNotProvidedException.java
new file mode 100644
index 0000000..4601711
--- /dev/null
+++ b/src/main/java/com/teragrep/cfe_16/exceptionhandling/ChannelNotProvidedException.java
@@ -0,0 +1,73 @@
+/*
+ * HTTP Event Capture to RFC5424 CFE_16
+ * Copyright (C) 2021 Suomen Kanuuna Oy
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see .
+ *
+ *
+ * Additional permission under GNU Affero General Public License version 3
+ * section 7
+ *
+ * If you modify this Program, or any covered work, by linking or combining it
+ * with other code, such other code is not for that reason alone subject to any
+ * of the requirements of the GNU Affero GPL version 3 as long as this Program
+ * is the same Program as licensed from Suomen Kanuuna Oy without any additional
+ * modifications.
+ *
+ * Supplemented terms under GNU Affero General Public License version 3
+ * section 7
+ *
+ * Origin of the software must be attributed to Suomen Kanuuna Oy. Any modified
+ * versions must be marked as "Modified version of" The Program.
+ *
+ * Names of the licensors and authors may not be used for publicity purposes.
+ *
+ * No rights are granted for use of trade names, trademarks, or service marks
+ * which are in The Program if any.
+ *
+ * Licensee must indemnify licensors and authors for any liability that these
+ * contractual assumptions impose on licensors and authors.
+ *
+ * To the extent this program is licensed as part of the Commercial versions of
+ * Teragrep, the applicable Commercial License may apply to this file if you as
+ * a licensee so wish it.
+ */
+
+package com.teragrep.cfe_16.exceptionhandling;
+
+@SuppressWarnings("serial")
+public class ChannelNotProvidedException extends RuntimeException {
+
+ public ChannelNotProvidedException() {
+ super();
+ }
+
+ public ChannelNotProvidedException(String message, Throwable cause, boolean enableSuppression,
+ boolean writableStackTrace) {
+ super(message, cause, enableSuppression, writableStackTrace);
+ }
+
+ public ChannelNotProvidedException(String message, Throwable cause) {
+ super(message, cause);
+ }
+
+ public ChannelNotProvidedException(String message) {
+ super(message);
+ }
+
+ public ChannelNotProvidedException(Throwable cause) {
+ super(cause);
+ }
+
+}
diff --git a/src/main/java/com/teragrep/cfe_16/exceptionhandling/EventFieldBlankException.java b/src/main/java/com/teragrep/cfe_16/exceptionhandling/EventFieldBlankException.java
new file mode 100644
index 0000000..ace281d
--- /dev/null
+++ b/src/main/java/com/teragrep/cfe_16/exceptionhandling/EventFieldBlankException.java
@@ -0,0 +1,73 @@
+/*
+ * HTTP Event Capture to RFC5424 CFE_16
+ * Copyright (C) 2021 Suomen Kanuuna Oy
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see .
+ *
+ *
+ * Additional permission under GNU Affero General Public License version 3
+ * section 7
+ *
+ * If you modify this Program, or any covered work, by linking or combining it
+ * with other code, such other code is not for that reason alone subject to any
+ * of the requirements of the GNU Affero GPL version 3 as long as this Program
+ * is the same Program as licensed from Suomen Kanuuna Oy without any additional
+ * modifications.
+ *
+ * Supplemented terms under GNU Affero General Public License version 3
+ * section 7
+ *
+ * Origin of the software must be attributed to Suomen Kanuuna Oy. Any modified
+ * versions must be marked as "Modified version of" The Program.
+ *
+ * Names of the licensors and authors may not be used for publicity purposes.
+ *
+ * No rights are granted for use of trade names, trademarks, or service marks
+ * which are in The Program if any.
+ *
+ * Licensee must indemnify licensors and authors for any liability that these
+ * contractual assumptions impose on licensors and authors.
+ *
+ * To the extent this program is licensed as part of the Commercial versions of
+ * Teragrep, the applicable Commercial License may apply to this file if you as
+ * a licensee so wish it.
+ */
+
+package com.teragrep.cfe_16.exceptionhandling;
+
+@SuppressWarnings("serial")
+public class EventFieldBlankException extends RuntimeException {
+
+ public EventFieldBlankException() {
+ super();
+ }
+
+ public EventFieldBlankException(String message, Throwable cause, boolean enableSuppression,
+ boolean writableStackTrace) {
+ super(message, cause, enableSuppression, writableStackTrace);
+ }
+
+ public EventFieldBlankException(String message, Throwable cause) {
+ super(message, cause);
+ }
+
+ public EventFieldBlankException(String message) {
+ super(message);
+ }
+
+ public EventFieldBlankException(Throwable cause) {
+ super(cause);
+ }
+
+}
diff --git a/src/main/java/com/teragrep/cfe_16/exceptionhandling/EventFieldMissingException.java b/src/main/java/com/teragrep/cfe_16/exceptionhandling/EventFieldMissingException.java
new file mode 100644
index 0000000..c1ddf83
--- /dev/null
+++ b/src/main/java/com/teragrep/cfe_16/exceptionhandling/EventFieldMissingException.java
@@ -0,0 +1,73 @@
+/*
+ * HTTP Event Capture to RFC5424 CFE_16
+ * Copyright (C) 2021 Suomen Kanuuna Oy
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see .
+ *
+ *
+ * Additional permission under GNU Affero General Public License version 3
+ * section 7
+ *
+ * If you modify this Program, or any covered work, by linking or combining it
+ * with other code, such other code is not for that reason alone subject to any
+ * of the requirements of the GNU Affero GPL version 3 as long as this Program
+ * is the same Program as licensed from Suomen Kanuuna Oy without any additional
+ * modifications.
+ *
+ * Supplemented terms under GNU Affero General Public License version 3
+ * section 7
+ *
+ * Origin of the software must be attributed to Suomen Kanuuna Oy. Any modified
+ * versions must be marked as "Modified version of" The Program.
+ *
+ * Names of the licensors and authors may not be used for publicity purposes.
+ *
+ * No rights are granted for use of trade names, trademarks, or service marks
+ * which are in The Program if any.
+ *
+ * Licensee must indemnify licensors and authors for any liability that these
+ * contractual assumptions impose on licensors and authors.
+ *
+ * To the extent this program is licensed as part of the Commercial versions of
+ * Teragrep, the applicable Commercial License may apply to this file if you as
+ * a licensee so wish it.
+ */
+
+package com.teragrep.cfe_16.exceptionhandling;
+
+@SuppressWarnings("serial")
+public class EventFieldMissingException extends RuntimeException {
+
+ public EventFieldMissingException() {
+ super();
+ }
+
+ public EventFieldMissingException(String message, Throwable cause, boolean enableSuppression,
+ boolean writableStackTrace) {
+ super(message, cause, enableSuppression, writableStackTrace);
+ }
+
+ public EventFieldMissingException(String message, Throwable cause) {
+ super(message, cause);
+ }
+
+ public EventFieldMissingException(String message) {
+ super(message);
+ }
+
+ public EventFieldMissingException(Throwable cause) {
+ super(cause);
+ }
+
+}
diff --git a/src/main/java/com/teragrep/cfe_16/exceptionhandling/HECErrorResponse.java b/src/main/java/com/teragrep/cfe_16/exceptionhandling/HECErrorResponse.java
new file mode 100644
index 0000000..f0512cd
--- /dev/null
+++ b/src/main/java/com/teragrep/cfe_16/exceptionhandling/HECErrorResponse.java
@@ -0,0 +1,92 @@
+/*
+ * HTTP Event Capture to RFC5424 CFE_16
+ * Copyright (C) 2021 Suomen Kanuuna Oy
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see .
+ *
+ *
+ * Additional permission under GNU Affero General Public License version 3
+ * section 7
+ *
+ * If you modify this Program, or any covered work, by linking or combining it
+ * with other code, such other code is not for that reason alone subject to any
+ * of the requirements of the GNU Affero GPL version 3 as long as this Program
+ * is the same Program as licensed from Suomen Kanuuna Oy without any additional
+ * modifications.
+ *
+ * Supplemented terms under GNU Affero General Public License version 3
+ * section 7
+ *
+ * Origin of the software must be attributed to Suomen Kanuuna Oy. Any modified
+ * versions must be marked as "Modified version of" The Program.
+ *
+ * Names of the licensors and authors may not be used for publicity purposes.
+ *
+ * No rights are granted for use of trade names, trademarks, or service marks
+ * which are in The Program if any.
+ *
+ * Licensee must indemnify licensors and authors for any liability that these
+ * contractual assumptions impose on licensors and authors.
+ *
+ * To the extent this program is licensed as part of the Commercial versions of
+ * Teragrep, the applicable Commercial License may apply to this file if you as
+ * a licensee so wish it.
+ */
+
+package com.teragrep.cfe_16.exceptionhandling;
+
+import com.fasterxml.jackson.annotation.JsonProperty;
+
+public class HECErrorResponse {
+
+ private String text;
+ private int code;
+ @JsonProperty("invalid-event-number")
+ private int invalidEventNumber;
+
+ public HECErrorResponse() {
+
+ }
+
+ public HECErrorResponse(String text, int code, int invalidEventNumber) {
+ this.code = code;
+ this.text = text;
+ this.invalidEventNumber = invalidEventNumber;
+ }
+
+ public String getText() {
+ return text;
+ }
+
+ public void setText(String text) {
+ this.text = text;
+ }
+
+ public int getCode() {
+ return code;
+ }
+
+ public void setCode(int code) {
+ this.code = code;
+ }
+
+ public int getInvalidEventNumber() {
+ return invalidEventNumber;
+ }
+
+ public void setInvalidEventNumber(int invalidEventNumber) {
+ this.invalidEventNumber = invalidEventNumber;
+ }
+
+}
diff --git a/src/main/java/com/teragrep/cfe_16/exceptionhandling/HECExceptionHandler.java b/src/main/java/com/teragrep/cfe_16/exceptionhandling/HECExceptionHandler.java
new file mode 100644
index 0000000..822651a
--- /dev/null
+++ b/src/main/java/com/teragrep/cfe_16/exceptionhandling/HECExceptionHandler.java
@@ -0,0 +1,129 @@
+/*
+ * HTTP Event Capture to RFC5424 CFE_16
+ * Copyright (C) 2021 Suomen Kanuuna Oy
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see .
+ *
+ *
+ * Additional permission under GNU Affero General Public License version 3
+ * section 7
+ *
+ * If you modify this Program, or any covered work, by linking or combining it
+ * with other code, such other code is not for that reason alone subject to any
+ * of the requirements of the GNU Affero GPL version 3 as long as this Program
+ * is the same Program as licensed from Suomen Kanuuna Oy without any additional
+ * modifications.
+ *
+ * Supplemented terms under GNU Affero General Public License version 3
+ * section 7
+ *
+ * Origin of the software must be attributed to Suomen Kanuuna Oy. Any modified
+ * versions must be marked as "Modified version of" The Program.
+ *
+ * Names of the licensors and authors may not be used for publicity purposes.
+ *
+ * No rights are granted for use of trade names, trademarks, or service marks
+ * which are in The Program if any.
+ *
+ * Licensee must indemnify licensors and authors for any liability that these
+ * contractual assumptions impose on licensors and authors.
+ *
+ * To the extent this program is licensed as part of the Commercial versions of
+ * Teragrep, the applicable Commercial License may apply to this file if you as
+ * a licensee so wish it.
+ */
+
+package com.teragrep.cfe_16.exceptionhandling;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.http.HttpStatus;
+import org.springframework.http.ResponseEntity;
+import org.springframework.web.bind.annotation.ControllerAdvice;
+import org.springframework.web.bind.annotation.ExceptionHandler;
+
+@ControllerAdvice
+public class HECExceptionHandler {
+ private static final Logger LOGGER = LoggerFactory.getLogger(HECExceptionHandler.class);
+ @ExceptionHandler
+ public ResponseEntity handleException(AuthenticationTokenMissingException exc) {
+
+ HECErrorResponse error = new HECErrorResponse("Token is required", 2, 0);
+
+ return new ResponseEntity<>(error, HttpStatus.UNAUTHORIZED);
+ }
+
+ @ExceptionHandler
+ public ResponseEntity handleException(ChannelNotFoundException exc) {
+
+ HECErrorResponse error = new HECErrorResponse("Invalid data channel", 11, 0);
+
+ return new ResponseEntity<>(error, HttpStatus.BAD_REQUEST);
+ }
+
+ @ExceptionHandler
+ public ResponseEntity handleException(SessionNotFoundException exc) {
+
+ HECErrorResponse error = new HECErrorResponse("Invalid token", 4, 0);
+
+ return new ResponseEntity<>(error, HttpStatus.FORBIDDEN);
+ }
+
+ @ExceptionHandler
+ public ResponseEntity handleException(ChannelNotProvidedException exc) {
+
+ HECErrorResponse error = new HECErrorResponse("Data channel is missing", 10, 0);
+
+ return new ResponseEntity<>(error, HttpStatus.BAD_REQUEST);
+ }
+
+ @ExceptionHandler
+ public ResponseEntity handleException(EventFieldMissingException exc) {
+
+ HECErrorResponse error = new HECErrorResponse("Event field is required", 12, 0);
+
+ return new ResponseEntity<>(error, HttpStatus.BAD_REQUEST);
+ }
+
+ @ExceptionHandler
+ public ResponseEntity handleException(EventFieldBlankException exc) {
+
+ HECErrorResponse error = new HECErrorResponse("Event field cannot be blank", 13, 0); // TODO: when support for
+ // multiple events with one
+ // request is implemented,
+ // get the real invalid
+ // event number here.
+ return new ResponseEntity<>(error, HttpStatus.BAD_REQUEST);
+ }
+
+ @ExceptionHandler
+ public ResponseEntity handleException(ServerIsBusyException exc) {
+
+ HECErrorResponse error = new HECErrorResponse("Server is busy", 9, 0); // TODO: when support for multiple events
+ // with one request is implemented,
+ // get the real invalid event number
+ // here.
+ return new ResponseEntity<>(error, HttpStatus.SERVICE_UNAVAILABLE);
+ }
+
+ @ExceptionHandler
+ public ResponseEntity handleException(InternalServerErrorException e) {
+
+ LOGGER.warn("Internal server error: ", e);
+ HECErrorResponse error = new HECErrorResponse("Internal server error ", 8, 0);
+
+ return new ResponseEntity<>(error, HttpStatus.INTERNAL_SERVER_ERROR);
+
+ }
+}
diff --git a/src/main/java/com/teragrep/cfe_16/exceptionhandling/InternalServerErrorException.java b/src/main/java/com/teragrep/cfe_16/exceptionhandling/InternalServerErrorException.java
new file mode 100644
index 0000000..3ad30ea
--- /dev/null
+++ b/src/main/java/com/teragrep/cfe_16/exceptionhandling/InternalServerErrorException.java
@@ -0,0 +1,80 @@
+/*
+ * HTTP Event Capture to RFC5424 CFE_16
+ * Copyright (C) 2021 Suomen Kanuuna Oy
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see .
+ *
+ *
+ * Additional permission under GNU Affero General Public License version 3
+ * section 7
+ *
+ * If you modify this Program, or any covered work, by linking or combining it
+ * with other code, such other code is not for that reason alone subject to any
+ * of the requirements of the GNU Affero GPL version 3 as long as this Program
+ * is the same Program as licensed from Suomen Kanuuna Oy without any additional
+ * modifications.
+ *
+ * Supplemented terms under GNU Affero General Public License version 3
+ * section 7
+ *
+ * Origin of the software must be attributed to Suomen Kanuuna Oy. Any modified
+ * versions must be marked as "Modified version of" The Program.
+ *
+ * Names of the licensors and authors may not be used for publicity purposes.
+ *
+ * No rights are granted for use of trade names, trademarks, or service marks
+ * which are in The Program if any.
+ *
+ * Licensee must indemnify licensors and authors for any liability that these
+ * contractual assumptions impose on licensors and authors.
+ *
+ * To the extent this program is licensed as part of the Commercial versions of
+ * Teragrep, the applicable Commercial License may apply to this file if you as
+ * a licensee so wish it.
+ */
+
+package com.teragrep.cfe_16.exceptionhandling;
+
+public class InternalServerErrorException extends RuntimeException {
+
+ /**
+ *
+ */
+ private static final long serialVersionUID = 1L;
+
+ public InternalServerErrorException() {
+ super();
+ // TODO Auto-generated constructor stub
+ }
+
+ public InternalServerErrorException(String message, Throwable cause, boolean enableSuppression, boolean writableStackTrace) {
+ super(message, cause, enableSuppression, writableStackTrace);
+ // TODO Auto-generated constructor stub
+ }
+
+ public InternalServerErrorException(String message, Throwable cause) {
+ super(message, cause);
+ // TODO Auto-generated constructor stub
+ }
+
+ public InternalServerErrorException(String message) {
+ super(message);
+ // TODO Auto-generated constructor stub
+ }
+
+ public InternalServerErrorException(Throwable cause) {
+ super(cause);
+ // TODO Auto-generated constructor stub
+ }
+}
diff --git a/src/main/java/com/teragrep/cfe_16/exceptionhandling/ServerIsBusyException.java b/src/main/java/com/teragrep/cfe_16/exceptionhandling/ServerIsBusyException.java
new file mode 100644
index 0000000..c491a99
--- /dev/null
+++ b/src/main/java/com/teragrep/cfe_16/exceptionhandling/ServerIsBusyException.java
@@ -0,0 +1,73 @@
+/*
+ * HTTP Event Capture to RFC5424 CFE_16
+ * Copyright (C) 2021 Suomen Kanuuna Oy
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see .
+ *
+ *
+ * Additional permission under GNU Affero General Public License version 3
+ * section 7
+ *
+ * If you modify this Program, or any covered work, by linking or combining it
+ * with other code, such other code is not for that reason alone subject to any
+ * of the requirements of the GNU Affero GPL version 3 as long as this Program
+ * is the same Program as licensed from Suomen Kanuuna Oy without any additional
+ * modifications.
+ *
+ * Supplemented terms under GNU Affero General Public License version 3
+ * section 7
+ *
+ * Origin of the software must be attributed to Suomen Kanuuna Oy. Any modified
+ * versions must be marked as "Modified version of" The Program.
+ *
+ * Names of the licensors and authors may not be used for publicity purposes.
+ *
+ * No rights are granted for use of trade names, trademarks, or service marks
+ * which are in The Program if any.
+ *
+ * Licensee must indemnify licensors and authors for any liability that these
+ * contractual assumptions impose on licensors and authors.
+ *
+ * To the extent this program is licensed as part of the Commercial versions of
+ * Teragrep, the applicable Commercial License may apply to this file if you as
+ * a licensee so wish it.
+ */
+
+package com.teragrep.cfe_16.exceptionhandling;
+
+@SuppressWarnings("serial")
+public class ServerIsBusyException extends RuntimeException {
+
+ public ServerIsBusyException() {
+ super();
+ }
+
+ public ServerIsBusyException(String message, Throwable cause, boolean enableSuppression,
+ boolean writableStackTrace) {
+ super(message, cause, enableSuppression, writableStackTrace);
+ }
+
+ public ServerIsBusyException(String message, Throwable cause) {
+ super(message, cause);
+ }
+
+ public ServerIsBusyException(String message) {
+ super(message);
+ }
+
+ public ServerIsBusyException(Throwable cause) {
+ super(cause);
+ }
+
+}
diff --git a/src/main/java/com/teragrep/cfe_16/exceptionhandling/SessionNotFoundException.java b/src/main/java/com/teragrep/cfe_16/exceptionhandling/SessionNotFoundException.java
new file mode 100644
index 0000000..a91e50e
--- /dev/null
+++ b/src/main/java/com/teragrep/cfe_16/exceptionhandling/SessionNotFoundException.java
@@ -0,0 +1,73 @@
+/*
+ * HTTP Event Capture to RFC5424 CFE_16
+ * Copyright (C) 2021 Suomen Kanuuna Oy
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see .
+ *
+ *
+ * Additional permission under GNU Affero General Public License version 3
+ * section 7
+ *
+ * If you modify this Program, or any covered work, by linking or combining it
+ * with other code, such other code is not for that reason alone subject to any
+ * of the requirements of the GNU Affero GPL version 3 as long as this Program
+ * is the same Program as licensed from Suomen Kanuuna Oy without any additional
+ * modifications.
+ *
+ * Supplemented terms under GNU Affero General Public License version 3
+ * section 7
+ *
+ * Origin of the software must be attributed to Suomen Kanuuna Oy. Any modified
+ * versions must be marked as "Modified version of" The Program.
+ *
+ * Names of the licensors and authors may not be used for publicity purposes.
+ *
+ * No rights are granted for use of trade names, trademarks, or service marks
+ * which are in The Program if any.
+ *
+ * Licensee must indemnify licensors and authors for any liability that these
+ * contractual assumptions impose on licensors and authors.
+ *
+ * To the extent this program is licensed as part of the Commercial versions of
+ * Teragrep, the applicable Commercial License may apply to this file if you as
+ * a licensee so wish it.
+ */
+
+package com.teragrep.cfe_16.exceptionhandling;
+
+@SuppressWarnings("serial")
+public class SessionNotFoundException extends RuntimeException {
+
+ public SessionNotFoundException() {
+ super();
+ }
+
+ public SessionNotFoundException(String message, Throwable cause, boolean enableSuppression,
+ boolean writableStackTrace) {
+ super(message, cause, enableSuppression, writableStackTrace);
+ }
+
+ public SessionNotFoundException(String message, Throwable cause) {
+ super(message, cause);
+ }
+
+ public SessionNotFoundException(String message) {
+ super(message);
+ }
+
+ public SessionNotFoundException(Throwable cause) {
+ super(cause);
+ }
+
+}
diff --git a/src/main/java/com/teragrep/cfe_16/rest/HECRestController.java b/src/main/java/com/teragrep/cfe_16/rest/HECRestController.java
new file mode 100644
index 0000000..70fb7b9
--- /dev/null
+++ b/src/main/java/com/teragrep/cfe_16/rest/HECRestController.java
@@ -0,0 +1,250 @@
+/*
+ * HTTP Event Capture to RFC5424 CFE_16
+ * Copyright (C) 2021 Suomen Kanuuna Oy
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see .
+ *
+ *
+ * Additional permission under GNU Affero General Public License version 3
+ * section 7
+ *
+ * If you modify this Program, or any covered work, by linking or combining it
+ * with other code, such other code is not for that reason alone subject to any
+ * of the requirements of the GNU Affero GPL version 3 as long as this Program
+ * is the same Program as licensed from Suomen Kanuuna Oy without any additional
+ * modifications.
+ *
+ * Supplemented terms under GNU Affero General Public License version 3
+ * section 7
+ *
+ * Origin of the software must be attributed to Suomen Kanuuna Oy. Any modified
+ * versions must be marked as "Modified version of" The Program.
+ *
+ * Names of the licensors and authors may not be used for publicity purposes.
+ *
+ * No rights are granted for use of trade names, trademarks, or service marks
+ * which are in The Program if any.
+ *
+ * Licensee must indemnify licensors and authors for any liability that these
+ * contractual assumptions impose on licensors and authors.
+ *
+ * To the extent this program is licensed as part of the Commercial versions of
+ * Teragrep, the applicable Commercial License may apply to this file if you as
+ * a licensee so wish it.
+ */
+
+package com.teragrep.cfe_16.rest;
+
+import com.fasterxml.jackson.databind.JsonNode;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.teragrep.cfe_16.RequestBodyCleaner;
+import com.teragrep.cfe_16.config.Configuration;
+import com.teragrep.cfe_16.service.HECService;
+import javax.servlet.http.HttpServletRequest;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.http.MediaType;
+import org.springframework.http.ResponseEntity;
+import org.springframework.util.MultiValueMap;
+import org.springframework.web.bind.annotation.*;
+
+@RestController
+@RequestMapping("/")
+public class HECRestController {
+ private static final Logger LOGGER = LoggerFactory.getLogger(HECRestController.class);
+ @Autowired
+ private HECService service;
+
+ @Autowired
+ private RequestBodyCleaner requestBodyCleaner;
+
+ @Autowired
+ private Configuration configuration;
+
+ private ObjectMapper objectMapper = new ObjectMapper();
+
+ @SuppressWarnings("rawtypes")
+ @RequestMapping(value = "services/collector", method = RequestMethod.POST, consumes = MediaType.APPLICATION_FORM_URLENCODED_VALUE)
+ public JsonNode sendEvents(HttpServletRequest request, @RequestBody MultiValueMap body,
+ @RequestParam(required = false) String channel) {
+ // TODO: Try to think an alternative way to implement getting the body of the
+ // call
+ String eventInJson = requestBodyCleaner.cleanAckRequestBody(body.toString(), channel);
+
+ long t1 = System.nanoTime();
+ JsonNode response = service.sendEvents(request, channel, eventInJson);
+ long t2 = System.nanoTime();
+ long dt = t2 - t1;
+ double us = (double)dt / 1000.0;
+ if (this.configuration.getPrintTimes()) {
+ LOGGER.info("sendEvents took <{}> nanoseconds, that is <{}> microseconds", dt, us);
+ }
+ return response;
+ }
+
+ @RequestMapping(value = "services/collector", method = RequestMethod.POST, consumes = MediaType.APPLICATION_JSON_VALUE)
+ public JsonNode sendEvents(HttpServletRequest request, @RequestBody String eventInJson,
+ @RequestParam(required = false) String channel) {
+ long t1 = System.nanoTime();
+ JsonNode response = service.sendEvents(request, channel, eventInJson);
+ long t2 = System.nanoTime();
+ long dt = t2 - t1;
+ double us = (double)dt / 1000.0;
+ if (this.configuration.getPrintTimes()) {
+ LOGGER.info("sendEvents took <{}> nanoseconds, that is <{}> microseconds", dt, us);
+ }
+ return response;
+ }
+
+ // @LogAnnotation(type = LogType.METRIC_DURATION)
+ @RequestMapping(value = "services/collector/ack", method = { RequestMethod.POST,
+ RequestMethod.GET }, consumes = MediaType.APPLICATION_JSON_VALUE)
+ public JsonNode getAcksWithPostMethod(@RequestBody JsonNode requestedAcksInJson, HttpServletRequest request,
+ @RequestParam(required = false) String channel) {
+
+ long t1 = System.nanoTime();
+ JsonNode response = service.getAcks(request, channel, requestedAcksInJson);
+ long t2 = System.nanoTime();
+ long dt = t2 - t1;
+ double us = (double)dt / 1000.0;
+ if (this.configuration.getPrintTimes()) {
+ LOGGER.info("getAcks took <{}> nanoseconds, that is <{}> microseconds", dt, us);
+ }
+ return response;
+ }
+
+ // @LogAnnotation(type = LogType.METRIC_DURATION)
+ @SuppressWarnings("rawtypes")
+ @RequestMapping(value = "services/collector/ack", method = { RequestMethod.POST,
+ RequestMethod.GET }, consumes = MediaType.APPLICATION_FORM_URLENCODED_VALUE)
+ public @ResponseBody JsonNode getAcks(@RequestBody MultiValueMap body, HttpServletRequest request,
+ @RequestParam(required = false) String channel) {
+ // TODO: Try to think an alternative way to implement getting the body of the
+ // call
+ String bodyString = requestBodyCleaner.cleanAckRequestBody(body.toString(), channel);
+
+ JsonNode requestedAcksInJson = null;
+ try {
+ requestedAcksInJson = objectMapper.readValue(bodyString, JsonNode.class);
+ } catch (Exception e) {
+ // TODO: handle the error in a proper way
+ LOGGER.warn("Failed to handle response: ", e);
+ }
+
+ long t1 = System.nanoTime();
+ JsonNode response = service.getAcks(request, channel, requestedAcksInJson);
+ long t2 = System.nanoTime();
+ long dt = t2 - t1;
+ double us = (double)dt / 1000.0;
+ if (this.configuration.getPrintTimes()) {
+ LOGGER.info("getAcks took <{}> nanoseconds, that is <{}> microseconds", dt, us);
+ }
+ return response;
+ }
+
+ @SuppressWarnings("rawtypes")
+ @RequestMapping(value = "services/collector/event", method = RequestMethod.POST, consumes = MediaType.APPLICATION_FORM_URLENCODED_VALUE)
+ public JsonNode sendEventsWithFormatOption(HttpServletRequest request, @RequestBody MultiValueMap body,
+ @RequestParam(required = false) String channel) {
+
+
+ // TODO: Try to think an alternative way to implement getting the body of the
+ // call
+ String eventInJson = requestBodyCleaner.cleanAckRequestBody(body.toString(), channel);
+
+ long t1 = System.nanoTime();
+ JsonNode response = service.sendEvents(request, channel, eventInJson);
+ long t2 = System.nanoTime();
+ long dt = t2 - t1;
+ double us = (double)dt / 1000.0;
+ if (this.configuration.getPrintTimes()) {
+ LOGGER.info("sendEvents took <{}> nanoseconds, that is <{}> microseconds", dt, us);
+ }
+ return response;
+ }
+
+ @RequestMapping(value = "services/collector/event", method = RequestMethod.POST, consumes = MediaType.APPLICATION_JSON_VALUE)
+ public JsonNode sendEventsWithFormatOption(HttpServletRequest request, @RequestBody String eventInJson,
+ @RequestParam(required = false) String channel) {
+ // FIXME: Fix implementation to known standards
+ // This endpoint works identically to services/collector but introduces a format
+ // option for future scalability.
+ long t1 = System.nanoTime();
+ JsonNode response = service.sendEvents(request, channel, eventInJson);
+ long t2 = System.nanoTime();
+ long dt = t2 - t1;
+ double us = (double)dt / 1000.0;
+ if (this.configuration.getPrintTimes()) {
+ LOGGER.info("sendEvents took <{}> nanoseconds, that is <{}> microseconds", dt, us);
+ }
+ return response;
+ }
+
+ // @LogAnnotation(type = LogType.METRIC_DURATION)
+ @PostMapping("services/collector/event/1.0")
+ public JsonNode sendEventsWithProtocolVersion(HttpServletRequest request, @RequestBody String eventInJson,
+ @RequestParam(required = false) String channel) {
+ // FIXME: Fix implementation to known standards
+ // This endpoint works identically to services/collector/event but introduces a
+ // protocol version for future scalability
+ long t1 = System.nanoTime();
+ JsonNode response = service.sendEvents(request, channel, eventInJson);
+ long t2 = System.nanoTime();
+ long dt = t2 - t1;
+ double us = (double)dt / 1000.0;
+ if (this.configuration.getPrintTimes()) {
+ LOGGER.info("sendEvents took <{}¦ nanoseconds, that is <{}> microseconds", dt, us);
+ }
+ return response;
+ }
+
+ // @LogAnnotation(type = LogType.METRIC_DURATION)
+ @GetMapping("services/collector/health")
+ public ResponseEntity getHealth(HttpServletRequest request) {
+ // FIXME: Fix implementation to known standards
+ return service.healthCheck(request);
+ }
+
+ // @LogAnnotation(type = LogType.METRIC_DURATION)
+ @GetMapping("services/collector/health/1.0")
+ public String getHealthWithProtocolVersion() {
+ // TODO: Implement endpoint
+ return null;
+ }
+
+ // @LogAnnotation(type = LogType.METRIC_DURATION)
+ @PostMapping("services/collector/mint")
+ public void sendMintData(@RequestBody String mintData) {
+ // TODO: Implement endpoint
+ }
+
+ // @LogAnnotation(type = LogType.METRIC_DURATION)
+ @PostMapping("services/collector/mint/1.0")
+ public void sendMintDataWithProtocolVersion(@RequestBody String mintData) {
+ // TODO: Implement endpoint
+ }
+
+ // @LogAnnotation(type = LogType.METRIC_DURATION)
+ @PostMapping("services/collector/raw")
+ public void sendRawData(@RequestBody String rawData) {
+ // TODO: Implement endpoint
+ }
+
+ // @LogAnnotation(type = LogType.METRIC_DURATION)
+ @PostMapping("services/collector/raw/1.0")
+ public void sendRawDataWithProtocolVersion(@RequestBody String rawData) {
+ // TODO: Implement endpoint
+ }
+}
diff --git a/src/main/java/com/teragrep/cfe_16/sender/AbstractSender.java b/src/main/java/com/teragrep/cfe_16/sender/AbstractSender.java
new file mode 100644
index 0000000..dd1fef5
--- /dev/null
+++ b/src/main/java/com/teragrep/cfe_16/sender/AbstractSender.java
@@ -0,0 +1,85 @@
+/*
+ * HTTP Event Capture to RFC5424 CFE_16
+ * Copyright (C) 2021 Suomen Kanuuna Oy
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see .
+ *
+ *
+ * Additional permission under GNU Affero General Public License version 3
+ * section 7
+ *
+ * If you modify this Program, or any covered work, by linking or combining it
+ * with other code, such other code is not for that reason alone subject to any
+ * of the requirements of the GNU Affero GPL version 3 as long as this Program
+ * is the same Program as licensed from Suomen Kanuuna Oy without any additional
+ * modifications.
+ *
+ * Supplemented terms under GNU Affero General Public License version 3
+ * section 7
+ *
+ * Origin of the software must be attributed to Suomen Kanuuna Oy. Any modified
+ * versions must be marked as "Modified version of" The Program.
+ *
+ * Names of the licensors and authors may not be used for publicity purposes.
+ *
+ * No rights are granted for use of trade names, trademarks, or service marks
+ * which are in The Program if any.
+ *
+ * Licensee must indemnify licensors and authors for any liability that these
+ * contractual assumptions impose on licensors and authors.
+ *
+ * To the extent this program is licensed as part of the Commercial versions of
+ * Teragrep, the applicable Commercial License may apply to this file if you as
+ * a licensee so wish it.
+ */
+
+package com.teragrep.cfe_16.sender;
+
+import com.cloudbees.syslog.SyslogMessage;
+import com.cloudbees.syslog.sender.AbstractSyslogMessageSender;
+
+import java.io.IOException;
+
+/**
+ * An abstract sender class for sending batch messages.
+ *
+ */
+public abstract class AbstractSender extends AbstractSyslogMessageSender {
+
+ protected String hostname;
+ protected int port;
+
+ protected AbstractSender(String hostname, int port) {
+ super();
+ this.hostname = hostname;
+ this.port = port;
+ }
+
+ /**
+ * Sends a batch of syslog messages.
+ *
+ * @param syslogMessages
+ */
+ public abstract void sendMessages(SyslogMessage[] syslogMessages) throws IOException;
+
+ @Override
+ public void setSyslogServerHostname(String syslogServerHostname) {
+ this.hostname = syslogServerHostname;
+ }
+
+ @Override
+ public void setSyslogServerPort(int syslogServerPort) {
+ this.port = syslogServerPort;
+ }
+}
diff --git a/src/main/java/com/teragrep/cfe_16/sender/RelpSender.java b/src/main/java/com/teragrep/cfe_16/sender/RelpSender.java
new file mode 100644
index 0000000..7b0d613
--- /dev/null
+++ b/src/main/java/com/teragrep/cfe_16/sender/RelpSender.java
@@ -0,0 +1,162 @@
+/*
+ * HTTP Event Capture to RFC5424 CFE_16
+ * Copyright (C) 2021 Suomen Kanuuna Oy
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see .
+ *
+ *
+ * Additional permission under GNU Affero General Public License version 3
+ * section 7
+ *
+ * If you modify this Program, or any covered work, by linking or combining it
+ * with other code, such other code is not for that reason alone subject to any
+ * of the requirements of the GNU Affero GPL version 3 as long as this Program
+ * is the same Program as licensed from Suomen Kanuuna Oy without any additional
+ * modifications.
+ *
+ * Supplemented terms under GNU Affero General Public License version 3
+ * section 7
+ *
+ * Origin of the software must be attributed to Suomen Kanuuna Oy. Any modified
+ * versions must be marked as "Modified version of" The Program.
+ *
+ * Names of the licensors and authors may not be used for publicity purposes.
+ *
+ * No rights are granted for use of trade names, trademarks, or service marks
+ * which are in The Program if any.
+ *
+ * Licensee must indemnify licensors and authors for any liability that these
+ * contractual assumptions impose on licensors and authors.
+ *
+ * To the extent this program is licensed as part of the Commercial versions of
+ * Teragrep, the applicable Commercial License may apply to this file if you as
+ * a licensee so wish it.
+ */
+
+package com.teragrep.cfe_16.sender;
+
+import com.cloudbees.syslog.SyslogMessage;
+import com.teragrep.cfe_16.rest.HECRestController;
+import com.teragrep.rlp_01.RelpBatch;
+import com.teragrep.rlp_01.RelpConnection;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.io.IOException;
+import java.nio.charset.StandardCharsets;
+import java.util.concurrent.TimeoutException;
+
+public class RelpSender extends AbstractSender {
+
+ private final RelpConnection sender;
+ private static final Logger LOGGER = LoggerFactory.getLogger(RelpSender.class);
+ //settings for timeouts, if they are 0 that we skip them
+ //default are 0
+ private int connectionTimeout = 10000;
+ private int readTimeout = 15000;
+ private int writeTimeout = 5000;
+ private int reconnectInterval = 500;
+
+ public RelpSender(String hostname, int port) {
+ super(hostname, port);
+ this.sender = new RelpConnection();
+ this.sender.setConnectionTimeout(connectionTimeout);
+ this.sender.setReadTimeout(this.readTimeout);
+ this.sender.setWriteTimeout(this.writeTimeout);
+ connect();
+ }
+
+ synchronized private void connect() {
+ boolean notConnected = true;
+ while (notConnected) {
+ boolean connected = false;
+ try {
+ LOGGER.debug("Connecting to RELP server");
+ connected = this.sender.connect(this.hostname, this.port);
+ } catch (Exception e) {
+ LOGGER.warn("Failed to connect to RELP Server: ", e);
+ }
+ if (connected) {
+ notConnected = false;
+ } else {
+ try {
+ LOGGER.debug("Sleeping for <[{}]> before reconnecting", this.reconnectInterval);
+ Thread.sleep(this.reconnectInterval);
+ } catch (InterruptedException e) {
+ LOGGER.warn("Sleep interrupted: ", e);
+ }
+ }
+ }
+ }
+
+ synchronized private void tearDown() {
+ LOGGER.debug("Tearing down connection");
+ this.sender.tearDown();
+ }
+
+ synchronized private void disconnect() {
+ try {
+ LOGGER.debug("Disconnecting from RELP server");
+ this.sender.disconnect();
+ } catch (IllegalStateException | IOException | TimeoutException e) {
+ LOGGER.warn("Failed to disconnect from RELP Server: ", e);
+ }
+ finally {
+ this.tearDown();
+ }
+ }
+
+ @Override
+ synchronized public void close() {
+ this.disconnect();
+ }
+
+ @Override
+ synchronized public void sendMessages(SyslogMessage[] syslogMessages) throws IOException {
+ final RelpBatch relpBatch = new RelpBatch();
+ for (SyslogMessage syslogMessage : syslogMessages) {
+ relpBatch.insert(syslogMessage.toRfc5424SyslogMessage().getBytes("UTF-8"));
+ }
+ doSend(relpBatch);
+ }
+
+ @Override
+ synchronized public void sendMessage(SyslogMessage syslogMessage) throws IOException {
+ final RelpBatch relpBatch = new RelpBatch();
+ relpBatch.insert(syslogMessage.toRfc5424SyslogMessage().getBytes("UTF-8"));
+ doSend(relpBatch);
+ }
+
+ synchronized private void doSend(RelpBatch relpBatch) {
+ boolean notSent = true;
+
+ while (notSent) {
+ try {
+ LOGGER.debug("Committing a RELP batch");
+ this.sender.commit(relpBatch);
+ } catch (IllegalStateException | IOException | TimeoutException e) {
+ LOGGER.warn("Failed to commit batch: ", e);
+ }
+
+ if (!relpBatch.verifyTransactionAll()) {
+ LOGGER.debug("Failed to verify all transactions, retrying them");
+ relpBatch.retryAllFailed();
+ this.tearDown();
+ this.connect();
+ } else {
+ notSent = false;
+ }
+ }
+ }
+}
diff --git a/src/main/java/com/teragrep/cfe_16/sender/SenderFactory.java b/src/main/java/com/teragrep/cfe_16/sender/SenderFactory.java
new file mode 100644
index 0000000..781a35c
--- /dev/null
+++ b/src/main/java/com/teragrep/cfe_16/sender/SenderFactory.java
@@ -0,0 +1,68 @@
+/*
+ * HTTP Event Capture to RFC5424 CFE_16
+ * Copyright (C) 2021 Suomen Kanuuna Oy
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see .
+ *
+ *
+ * Additional permission under GNU Affero General Public License version 3
+ * section 7
+ *
+ * If you modify this Program, or any covered work, by linking or combining it
+ * with other code, such other code is not for that reason alone subject to any
+ * of the requirements of the GNU Affero GPL version 3 as long as this Program
+ * is the same Program as licensed from Suomen Kanuuna Oy without any additional
+ * modifications.
+ *
+ * Supplemented terms under GNU Affero General Public License version 3
+ * section 7
+ *
+ * Origin of the software must be attributed to Suomen Kanuuna Oy. Any modified
+ * versions must be marked as "Modified version of" The Program.
+ *
+ * Names of the licensors and authors may not be used for publicity purposes.
+ *
+ * No rights are granted for use of trade names, trademarks, or service marks
+ * which are in The Program if any.
+ *
+ * Licensee must indemnify licensors and authors for any liability that these
+ * contractual assumptions impose on licensors and authors.
+ *
+ * To the extent this program is licensed as part of the Commercial versions of
+ * Teragrep, the applicable Commercial License may apply to this file if you as
+ * a licensee so wish it.
+ */
+
+package com.teragrep.cfe_16.sender;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.io.IOException;
+
+public class SenderFactory {
+ private static final Logger LOGGER = LoggerFactory.getLogger(SenderFactory.class);
+ public static AbstractSender createSender(String type, String hostname, int port) throws IOException {
+ LOGGER.debug("Creating sender for type <[{}]> to <[{}]>:<[{}]>", type, hostname, port);
+ if (type.equalsIgnoreCase("UDP")) {
+ return new UdpSender(hostname, port);
+ } else if (type.equalsIgnoreCase("TCP")) {
+ return new TcpSender(hostname, port);
+ } else if (type.equalsIgnoreCase("RELP")) {
+ return new RelpSender(hostname, port);
+ } else {
+ throw new IOException("Invalid sender type: " + type);
+ }
+ }
+}
diff --git a/src/main/java/com/teragrep/cfe_16/sender/TcpSender.java b/src/main/java/com/teragrep/cfe_16/sender/TcpSender.java
new file mode 100644
index 0000000..11f05b5
--- /dev/null
+++ b/src/main/java/com/teragrep/cfe_16/sender/TcpSender.java
@@ -0,0 +1,91 @@
+/*
+ * HTTP Event Capture to RFC5424 CFE_16
+ * Copyright (C) 2021 Suomen Kanuuna Oy
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see .
+ *
+ *
+ * Additional permission under GNU Affero General Public License version 3
+ * section 7
+ *
+ * If you modify this Program, or any covered work, by linking or combining it
+ * with other code, such other code is not for that reason alone subject to any
+ * of the requirements of the GNU Affero GPL version 3 as long as this Program
+ * is the same Program as licensed from Suomen Kanuuna Oy without any additional
+ * modifications.
+ *
+ * Supplemented terms under GNU Affero General Public License version 3
+ * section 7
+ *
+ * Origin of the software must be attributed to Suomen Kanuuna Oy. Any modified
+ * versions must be marked as "Modified version of" The Program.
+ *
+ * Names of the licensors and authors may not be used for publicity purposes.
+ *
+ * No rights are granted for use of trade names, trademarks, or service marks
+ * which are in The Program if any.
+ *
+ * Licensee must indemnify licensors and authors for any liability that these
+ * contractual assumptions impose on licensors and authors.
+ *
+ * To the extent this program is licensed as part of the Commercial versions of
+ * Teragrep, the applicable Commercial License may apply to this file if you as
+ * a licensee so wish it.
+ */
+
+package com.teragrep.cfe_16.sender;
+
+import com.cloudbees.syslog.SyslogMessage;
+import com.cloudbees.syslog.sender.TcpSyslogMessageSender;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.io.IOException;
+
+public class TcpSender extends AbstractSender {
+ private static final Logger LOGGER = LoggerFactory.getLogger(TcpSender.class);
+ private TcpSyslogMessageSender sender;
+
+ public TcpSender(String hostname, int port) {
+ super(hostname, port);
+ this.sender = new TcpSyslogMessageSender();
+ this.sender.setSyslogServerHostname(this.hostname);
+ this.sender.setSyslogServerPort(this.port);
+ }
+
+ @Override
+ public void sendMessages(SyslogMessage[] syslogMessages) throws IOException {
+ LOGGER.debug("Sending messages");
+ for (SyslogMessage syslogMessage : syslogMessages) {
+ this.sender.sendMessage(syslogMessage);
+ }
+ }
+
+ @Override
+ public void sendMessage(SyslogMessage syslogMessage) throws IOException {
+ LOGGER.debug("Sending message");
+ this.sender.sendMessage(syslogMessage);
+ }
+
+ @Override
+ public void close() throws IOException {
+ LOGGER.debug("Closing sender");
+ this.sender.close();
+ }
+
+ public void setSsl(boolean ssl) {
+ LOGGER.debug("Set Ssl to <{}>", ssl);
+ this.sender.setSsl(ssl);
+ }
+}
diff --git a/src/main/java/com/teragrep/cfe_16/sender/UdpSender.java b/src/main/java/com/teragrep/cfe_16/sender/UdpSender.java
new file mode 100644
index 0000000..7c7d310
--- /dev/null
+++ b/src/main/java/com/teragrep/cfe_16/sender/UdpSender.java
@@ -0,0 +1,86 @@
+/*
+ * HTTP Event Capture to RFC5424 CFE_16
+ * Copyright (C) 2021 Suomen Kanuuna Oy
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see .
+ *
+ *
+ * Additional permission under GNU Affero General Public License version 3
+ * section 7
+ *
+ * If you modify this Program, or any covered work, by linking or combining it
+ * with other code, such other code is not for that reason alone subject to any
+ * of the requirements of the GNU Affero GPL version 3 as long as this Program
+ * is the same Program as licensed from Suomen Kanuuna Oy without any additional
+ * modifications.
+ *
+ * Supplemented terms under GNU Affero General Public License version 3
+ * section 7
+ *
+ * Origin of the software must be attributed to Suomen Kanuuna Oy. Any modified
+ * versions must be marked as "Modified version of" The Program.
+ *
+ * Names of the licensors and authors may not be used for publicity purposes.
+ *
+ * No rights are granted for use of trade names, trademarks, or service marks
+ * which are in The Program if any.
+ *
+ * Licensee must indemnify licensors and authors for any liability that these
+ * contractual assumptions impose on licensors and authors.
+ *
+ * To the extent this program is licensed as part of the Commercial versions of
+ * Teragrep, the applicable Commercial License may apply to this file if you as
+ * a licensee so wish it.
+ */
+
+package com.teragrep.cfe_16.sender;
+
+import com.cloudbees.syslog.SyslogMessage;
+import com.cloudbees.syslog.sender.UdpSyslogMessageSender;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.io.IOException;
+
+public class UdpSender extends AbstractSender {
+ private static final Logger LOGGER = LoggerFactory.getLogger(UdpSender.class);
+ private UdpSyslogMessageSender sender;
+
+ public UdpSender(String hostname, int port) {
+ super(hostname, port);
+ this.sender = new UdpSyslogMessageSender();
+ this.sender.setSyslogServerHostname(this.hostname);
+ this.sender.setSyslogServerPort(this.port);
+ }
+
+ @Override
+ public void sendMessages(SyslogMessage[] syslogMessages) throws IOException {
+ LOGGER.debug("Sending messages");
+ for (SyslogMessage syslogMessage : syslogMessages) {
+ this.sender.sendMessage(syslogMessage);
+ }
+ }
+
+ @Override
+ public void sendMessage(SyslogMessage syslogMessage) throws IOException {
+ LOGGER.debug("Sending message");
+ this.sender.sendMessage(syslogMessage);
+ }
+
+ @Override
+ public void close() throws IOException {
+ LOGGER.debug("Closing sender");
+ this.sender.close();
+ }
+}
diff --git a/src/main/java/com/teragrep/cfe_16/service/HECService.java b/src/main/java/com/teragrep/cfe_16/service/HECService.java
new file mode 100644
index 0000000..a6625bf
--- /dev/null
+++ b/src/main/java/com/teragrep/cfe_16/service/HECService.java
@@ -0,0 +1,87 @@
+/*
+ * HTTP Event Capture to RFC5424 CFE_16
+ * Copyright (C) 2021 Suomen Kanuuna Oy
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see .
+ *
+ *
+ * Additional permission under GNU Affero General Public License version 3
+ * section 7
+ *
+ * If you modify this Program, or any covered work, by linking or combining it
+ * with other code, such other code is not for that reason alone subject to any
+ * of the requirements of the GNU Affero GPL version 3 as long as this Program
+ * is the same Program as licensed from Suomen Kanuuna Oy without any additional
+ * modifications.
+ *
+ * Supplemented terms under GNU Affero General Public License version 3
+ * section 7
+ *
+ * Origin of the software must be attributed to Suomen Kanuuna Oy. Any modified
+ * versions must be marked as "Modified version of" The Program.
+ *
+ * Names of the licensors and authors may not be used for publicity purposes.
+ *
+ * No rights are granted for use of trade names, trademarks, or service marks
+ * which are in The Program if any.
+ *
+ * Licensee must indemnify licensors and authors for any liability that these
+ * contractual assumptions impose on licensors and authors.
+ *
+ * To the extent this program is licensed as part of the Commercial versions of
+ * Teragrep, the applicable Commercial License may apply to this file if you as
+ * a licensee so wish it.
+ */
+
+package com.teragrep.cfe_16.service;
+
+
+import com.fasterxml.jackson.databind.JsonNode;
+import com.fasterxml.jackson.databind.node.ObjectNode;
+import javax.servlet.http.HttpServletRequest;
+import org.springframework.http.ResponseEntity;
+
+/**
+ * An interface that specified the REST back end API.
+ *
+ */
+public interface HECService {
+
+ /**
+ * Returns the JSON object as a response of given HTTP event request.
+ *
+ * @param request
+ * @param channel
+ * @param eventInJson
+ * @return
+ */
+ public ObjectNode sendEvents(HttpServletRequest request, String channel, String eventInJson);
+
+ /**
+ *
+ * @param request
+ * @param channel
+ * @param requestedAcksInJson
+ * @return
+ */
+ public JsonNode getAcks(HttpServletRequest request, String channel, JsonNode requestedAcksInJson);
+
+ /**
+ * Ping.
+ *
+ * @param request
+ * @return
+ */
+ public ResponseEntity healthCheck(HttpServletRequest request);
+}
diff --git a/src/main/java/com/teragrep/cfe_16/service/HECServiceImpl.java b/src/main/java/com/teragrep/cfe_16/service/HECServiceImpl.java
new file mode 100644
index 0000000..aba5012
--- /dev/null
+++ b/src/main/java/com/teragrep/cfe_16/service/HECServiceImpl.java
@@ -0,0 +1,197 @@
+/*
+ * HTTP Event Capture to RFC5424 CFE_16
+ * Copyright (C) 2021 Suomen Kanuuna Oy
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see .
+ *
+ *
+ * Additional permission under GNU Affero General Public License version 3
+ * section 7
+ *
+ * If you modify this Program, or any covered work, by linking or combining it
+ * with other code, such other code is not for that reason alone subject to any
+ * of the requirements of the GNU Affero GPL version 3 as long as this Program
+ * is the same Program as licensed from Suomen Kanuuna Oy without any additional
+ * modifications.
+ *
+ * Supplemented terms under GNU Affero General Public License version 3
+ * section 7
+ *
+ * Origin of the software must be attributed to Suomen Kanuuna Oy. Any modified
+ * versions must be marked as "Modified version of" The Program.
+ *
+ * Names of the licensors and authors may not be used for publicity purposes.
+ *
+ * No rights are granted for use of trade names, trademarks, or service marks
+ * which are in The Program if any.
+ *
+ * Licensee must indemnify licensors and authors for any liability that these
+ * contractual assumptions impose on licensors and authors.
+ *
+ * To the extent this program is licensed as part of the Commercial versions of
+ * Teragrep, the applicable Commercial License may apply to this file if you as
+ * a licensee so wish it.
+ */
+
+package com.teragrep.cfe_16.service;
+
+import com.fasterxml.jackson.databind.JsonNode;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.fasterxml.jackson.databind.node.ObjectNode;
+import com.teragrep.cfe_16.*;
+import com.teragrep.cfe_16.bo.HeaderInfo;
+import com.teragrep.cfe_16.bo.Session;
+import com.teragrep.cfe_16.exceptionhandling.AuthenticationTokenMissingException;
+import com.teragrep.cfe_16.exceptionhandling.ChannelNotFoundException;
+import com.teragrep.cfe_16.exceptionhandling.ChannelNotProvidedException;
+import com.teragrep.cfe_16.exceptionhandling.SessionNotFoundException;
+import javax.servlet.http.HttpServletRequest;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.http.HttpStatus;
+import org.springframework.http.ResponseEntity;
+import org.springframework.stereotype.Service;
+
+/**
+ * Implementation of the REST Service back end.
+ *
+ */
+@Service
+public class HECServiceImpl implements HECService {
+ private static final Logger LOGGER = LoggerFactory.getLogger(HECServiceImpl.class);
+ @Autowired
+ private AckManager ackManager;
+
+ @Autowired
+ private SessionManager sessionManager;
+
+ @Autowired
+ private TokenManager tokenManager;
+
+ @Autowired
+ private EventManager eventManager;
+
+ @Autowired
+ private RequestHandler requestHandler;
+
+ private ObjectMapper objectMapper = new ObjectMapper();
+
+ public HECServiceImpl() {
+ }
+
+ @Override
+ // @LogAnnotation(type = LogType.METRIC_COUNTER)
+ public ObjectNode sendEvents(HttpServletRequest request,
+ String channel,
+ String eventInJson) {
+ LOGGER.debug("Sending events to channel <{}>", channel);
+ if (this.tokenManager.tokenIsMissing(request)) {
+ throw new AuthenticationTokenMissingException("Authentication token must be provided");
+ }
+ // AspectLoggerWrapper.logMetricCounter(null, "metric_counter", 10);
+ // AspectLoggerWrapper.logMetricDuration(null, "new_metric",
+ // MetricDurationOptionsImpl.MetricDuration.P10S);
+ String authHeader = request.getHeader("Authorization");
+ HeaderInfo headerInfo = requestHandler.createHeaderInfoObject(request);
+
+ String authToken;
+ if (tokenManager.isTokenInBasic(authHeader)) {
+ LOGGER.debug("Token was provided via Basic");
+ authToken = this.tokenManager.getTokenFromBasic(authHeader);
+ } else {
+ LOGGER.debug("Token was provided via header");
+ authToken = authHeader;
+ }
+
+ // if there is no channel, we'll use the default channel
+ if (channel == null) {
+ channel = Session.DEFAULT_CHANNEL;
+ LOGGER.debug("Channel was not provided, using <{}>", channel);
+ }
+
+ Session session = this.sessionManager.getOrCreateSession(authToken);
+
+ // if the channel is not in the session, let's add the channel into it
+ if (!session.doesChannelExist(channel)) {
+ LOGGER.debug("Adding channel <{}>", channel);
+ session.addChannel(channel);
+ }
+
+ // TODO: find a nice way of not passing AckManager instance
+ ObjectNode ackNode = this.eventManager.convertData(authToken,
+ channel,
+ eventInJson,
+ headerInfo,
+ this.ackManager);
+
+ return ackNode;
+ }
+
+ // @LogAnnotation(type = LogType.RESPONSE)
+ @SuppressWarnings("deprecation")
+ @Override
+ public JsonNode getAcks(HttpServletRequest request,
+ String channel,
+ JsonNode requestedAcksInJson) {
+
+ // filter out error cases
+ // authentication header is required always
+ if (this.tokenManager.tokenIsMissing(request)) {
+ throw new AuthenticationTokenMissingException("Authentication token must be provided");
+ }
+
+ String authHeader = request.getHeader("Authorization");
+
+ String authToken;
+ if (tokenManager.isTokenInBasic(authHeader)) {
+ LOGGER.debug("Token was provided via Basic");
+ authToken = this.tokenManager.getTokenFromBasic(authHeader);
+ } else {
+ LOGGER.debug("Token was provided via header");
+ authToken = authHeader;
+ }
+
+ // channel is required
+ if (channel == null) {
+ throw new ChannelNotProvidedException("Channel must be provided when requesting ack statuses");
+ }
+
+ // session is also required
+ Session session = this.sessionManager.getSession(authToken);
+ if (session == null) {
+ throw new SessionNotFoundException("Session not found for auth token " + authToken);
+ }
+
+ // if channel is not inside Session, it is considered an error case
+ if (!session.doesChannelExist(channel)) {
+ throw new ChannelNotFoundException();
+ }
+ session.touch();
+
+ ObjectNode responseNode = objectMapper.createObjectNode();
+ JsonNode requestedAckStatuses = this.ackManager.getRequestedAckStatuses(authToken, channel, requestedAcksInJson);
+ responseNode.put("acks", requestedAckStatuses);
+ return responseNode;
+ }
+
+ // @LogAnnotation(type = LogType.RESPONSE)
+ @Override
+ public ResponseEntity healthCheck(HttpServletRequest request) {
+ if (this.tokenManager.tokenIsMissing(request)) {
+ return new ResponseEntity("Invalid HEC token", HttpStatus.BAD_REQUEST);
+ }
+ return new ResponseEntity("HEC is available and accepting input", HttpStatus.OK);
+ }
+}
diff --git a/src/main/resources/application.properties.example b/src/main/resources/application.properties.example
new file mode 100644
index 0000000..8ba1f51
--- /dev/null
+++ b/src/main/resources/application.properties.example
@@ -0,0 +1,11 @@
+syslog.server.host=127.0.0.1
+syslog.server.port=1234
+syslog.server.protocol=TCP
+max.channels=1000000
+max.ack.value=1000000
+max.ack.age=20000
+max.session.age=30000
+poll.time=300000
+config.poll.time=5000
+spring.devtools.add-properties=false
+server.print.times=true
diff --git a/src/main/resources/control.sh b/src/main/resources/control.sh
new file mode 100755
index 0000000..4f2be82
--- /dev/null
+++ b/src/main/resources/control.sh
@@ -0,0 +1,159 @@
+#!/usr/bin/bash
+#
+# HTTP Event Capture to RFC5424 CFE_16
+# Copyright (C) 2021 Suomen Kanuuna Oy
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Affero General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU Affero General Public License for more details.
+#
+# You should have received a copy of the GNU Affero General Public License
+# along with this program. If not, see .
+#
+#
+# Additional permission under GNU Affero General Public License version 3
+# section 7
+#
+# If you modify this Program, or any covered work, by linking or combining it
+# with other code, such other code is not for that reason alone subject to any
+# of the requirements of the GNU Affero GPL version 3 as long as this Program
+# is the same Program as licensed from Suomen Kanuuna Oy without any additional
+# modifications.
+#
+# Supplemented terms under GNU Affero General Public License version 3
+# section 7
+#
+# Origin of the software must be attributed to Suomen Kanuuna Oy. Any modified
+# versions must be marked as "Modified version of" The Program.
+#
+# Names of the licensors and authors may not be used for publicity purposes.
+#
+# No rights are granted for use of trade names, trademarks, or service marks
+# which are in The Program if any.
+#
+# Licensee must indemnify licensors and authors for any liability that these
+# contractual assumptions impose on licensors and authors.
+#
+# To the extent this program is licensed as part of the Commercial versions of
+# Teragrep, the applicable Commercial License may apply to this file if you as
+# a licensee so wish it.
+#
+
+ulimit -n 65535
+
+RETVAL=0
+prog="Teragrep HTTP Event Capture"
+
+start() {
+ echo "Starting $prog: "
+
+ if [ ! -r /opt/teragrep/cfe_16/etc/application.properties ]; then
+ echo "Configuration file /opt/teragrep/cfe_16/etc/application.properties does not exist"
+ exit 1
+ fi
+
+ if [ -r /opt/teragrep/cfe_16/var/cfe_16.pid ]; then
+ # check if running
+ cfe16_pid=$(cat /opt/teragrep/cfe_16/var/cfe_16.pid)
+
+ (ps -f -p $cfe16_pid | grep cfe_16.jar > /dev/null 2>&1)
+ running=$?
+
+ if [ "$running" = 0 ]; then
+ echo "Already running with ${cfe16_pid}"
+ RETVAL=1
+ return 1
+ fi
+ fi
+
+ ##
+ GENERAL_OPTS="-d64"
+ HEAPSIZE_OPTS="-Xmx192M -Xms192M"
+ AGENT_OPTS="-javaagent:/opt/teragrep/cfe_16/share/aspectjweaver.jar"
+
+ #JMX_OPTS="-Dcom.sun.management.jmxremote -Dcom.sun.management.jmxremote.authenticate=false -Dcom.sun.management.jmxremote.ssl=false -Dcom.sun.management.jmxremote.port=1100 -Djava.rmi.server.hostname=localhost"
+
+ GC_OPTS="-XX:+UseConcMarkSweepGC -XX:+CMSIncrementalPacing -XX:+CMSParallelRemarkEnabled -XX:+CMSClassUnloadingEnabled"
+ GC_DEBUG_OPTS="" # -XX:+PrintTenuringDistribution -XX:+PrintGCDetails -XX:+PrintGCTimeStamps -XX:+PrintTLAB
+
+ /usr/bin/nohup /usr/bin/java $GENERAL_OPTS $HEAPSIZE_OPTS $AGENT_OPTS $JMX_OPTS $THREAD_OPTS $GC_OPTS $GC_DEBUG_OPTS -jar /opt/teragrep/cfe_16/share/cfe_16.jar --spring.config.location=file:///opt/teragrep/cfe_16/etc/application.properties > /opt/teragrep/cfe_16/var/stdout.log 2> /opt/teragrep/cfe_16/var/stderr.log &
+ RETVAL=$?
+
+ # store pid
+ echo $! > /opt/teragrep/cfe_16/var/cfe_16.pid
+
+ [ "$RETVAL" = 0 ] && echo "OK"
+ echo
+}
+
+stop() {
+ RETVAL=1
+ cfe16_pid=""
+ echo "Stopping $prog: "
+
+ if [ -r /opt/teragrep/cfe_16/var/cfe_16.pid ]; then
+ # check if running
+ cfe16_pid=$(cat /opt/teragrep/cfe_16/var/cfe_16.pid)
+
+ (ps -f -p $cfe16_pid | grep cfe_16.jar > /dev/null 2>&1)
+ running=$?
+
+ if [ "$running" = 0 ]; then
+ kill $cfe16_pid
+ while `ps -p $cfe16_pid > /dev/null 2>&1`; do sleep 1; done
+ RETVAL=$?
+ fi
+ fi
+
+ [ "$RETVAL" = 0 ] && echo "OK"
+ echo
+}
+
+status() {
+ RETVAL=1
+ echo "$prog status: "
+
+ if [ -r /opt/teragrep/cfe_16/var/cfe_16.pid ]; then
+ # check if running
+ cfe16_pid=$(cat /opt/teragrep/cfe_16/var/cfe_16.pid)
+
+ (ps -f -p $cfe16_pid | grep cfe_16.jar > /dev/null 2>&1)
+ running=$?
+
+ if [ "$running" = 0 ]; then
+ echo "Pid: ${cfe16_pid}"
+ RETVAL=0
+ return 0
+ fi
+ fi
+ echo
+}
+
+case "$1" in
+ start)
+ start
+ ;;
+ stop)
+ stop
+ ;;
+ restart)
+ stop
+ start
+ ;;
+ reload)
+ reload
+ ;;
+ status)
+ status
+ ;;
+ *)
+ echo "Usage: $0 {start|stop|restart|reload|status}"
+ RETVAL=1
+ ;;
+esac
diff --git a/src/main/resources/log4j2.xml b/src/main/resources/log4j2.xml
new file mode 100644
index 0000000..28bd669
--- /dev/null
+++ b/src/main/resources/log4j2.xml
@@ -0,0 +1,20 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/test/java/com/teragrep/cfe_16/Cfe16ApplicationTests.java b/src/test/java/com/teragrep/cfe_16/Cfe16ApplicationTests.java
new file mode 100644
index 0000000..4fa737c
--- /dev/null
+++ b/src/test/java/com/teragrep/cfe_16/Cfe16ApplicationTests.java
@@ -0,0 +1,56 @@
+/*
+ * HTTP Event Capture to RFC5424 CFE_16
+ * Copyright (C) 2021 Suomen Kanuuna Oy
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see .
+ *
+ *
+ * Additional permission under GNU Affero General Public License version 3
+ * section 7
+ *
+ * If you modify this Program, or any covered work, by linking or combining it
+ * with other code, such other code is not for that reason alone subject to any
+ * of the requirements of the GNU Affero GPL version 3 as long as this Program
+ * is the same Program as licensed from Suomen Kanuuna Oy without any additional
+ * modifications.
+ *
+ * Supplemented terms under GNU Affero General Public License version 3
+ * section 7
+ *
+ * Origin of the software must be attributed to Suomen Kanuuna Oy. Any modified
+ * versions must be marked as "Modified version of" The Program.
+ *
+ * Names of the licensors and authors may not be used for publicity purposes.
+ *
+ * No rights are granted for use of trade names, trademarks, or service marks
+ * which are in The Program if any.
+ *
+ * Licensee must indemnify licensors and authors for any liability that these
+ * contractual assumptions impose on licensors and authors.
+ *
+ * To the extent this program is licensed as part of the Commercial versions of
+ * Teragrep, the applicable Commercial License may apply to this file if you as
+ * a licensee so wish it.
+ */
+
+package com.teragrep.cfe_16;
+
+import org.junit.jupiter.api.Test;
+
+public class Cfe16ApplicationTests {
+
+ @Test
+ public void contextLoads() {
+ }
+}
diff --git a/src/test/java/com/teragrep/cfe_16/CleanRequestBodyTests.java b/src/test/java/com/teragrep/cfe_16/CleanRequestBodyTests.java
new file mode 100644
index 0000000..a6f9e36
--- /dev/null
+++ b/src/test/java/com/teragrep/cfe_16/CleanRequestBodyTests.java
@@ -0,0 +1,61 @@
+/*
+ * HTTP Event Capture to RFC5424 CFE_16
+ * Copyright (C) 2021 Suomen Kanuuna Oy
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see .
+ *
+ *
+ * Additional permission under GNU Affero General Public License version 3
+ * section 7
+ *
+ * If you modify this Program, or any covered work, by linking or combining it
+ * with other code, such other code is not for that reason alone subject to any
+ * of the requirements of the GNU Affero GPL version 3 as long as this Program
+ * is the same Program as licensed from Suomen Kanuuna Oy without any additional
+ * modifications.
+ *
+ * Supplemented terms under GNU Affero General Public License version 3
+ * section 7
+ *
+ * Origin of the software must be attributed to Suomen Kanuuna Oy. Any modified
+ * versions must be marked as "Modified version of" The Program.
+ *
+ * Names of the licensors and authors may not be used for publicity purposes.
+ *
+ * No rights are granted for use of trade names, trademarks, or service marks
+ * which are in The Program if any.
+ *
+ * Licensee must indemnify licensors and authors for any liability that these
+ * contractual assumptions impose on licensors and authors.
+ *
+ * To the extent this program is licensed as part of the Commercial versions of
+ * Teragrep, the applicable Commercial License may apply to this file if you as
+ * a licensee so wish it.
+ */
+
+package com.teragrep.cfe_16;
+
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.Test;
+
+public class CleanRequestBodyTests {
+ @Test
+ public void testCleanRequestBodyNormal() {
+ String input = "{channel=[CHANNEL_11111], {\"sourcetype\": \"mysourcetype\", \"event\": \"Hello, world!\"}=[]}";
+ String channel = "CHANNEL_11111";
+ RequestBodyCleaner requestBodyCleaner = new RequestBodyCleaner();
+ String cleaned = requestBodyCleaner.cleanAckRequestBody(input, channel);
+ Assertions.assertEquals("{\"sourcetype\": \"mysourcetype\", \"event\": \"Hello, world!\"}", cleaned, "Did not clean channel properly");
+ }
+}
diff --git a/src/test/java/com/teragrep/cfe_16/ConverterTests.java b/src/test/java/com/teragrep/cfe_16/ConverterTests.java
new file mode 100644
index 0000000..9b1630b
--- /dev/null
+++ b/src/test/java/com/teragrep/cfe_16/ConverterTests.java
@@ -0,0 +1,371 @@
+/*
+ * HTTP Event Capture to RFC5424 CFE_16
+ * Copyright (C) 2021 Suomen Kanuuna Oy
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see .
+ *
+ *
+ * Additional permission under GNU Affero General Public License version 3
+ * section 7
+ *
+ * If you modify this Program, or any covered work, by linking or combining it
+ * with other code, such other code is not for that reason alone subject to any
+ * of the requirements of the GNU Affero GPL version 3 as long as this Program
+ * is the same Program as licensed from Suomen Kanuuna Oy without any additional
+ * modifications.
+ *
+ * Supplemented terms under GNU Affero General Public License version 3
+ * section 7
+ *
+ * Origin of the software must be attributed to Suomen Kanuuna Oy. Any modified
+ * versions must be marked as "Modified version of" The Program.
+ *
+ * Names of the licensors and authors may not be used for publicity purposes.
+ *
+ * No rights are granted for use of trade names, trademarks, or service marks
+ * which are in The Program if any.
+ *
+ * Licensee must indemnify licensors and authors for any liability that these
+ * contractual assumptions impose on licensors and authors.
+ *
+ * To the extent this program is licensed as part of the Commercial versions of
+ * Teragrep, the applicable Commercial License may apply to this file if you as
+ * a licensee so wish it.
+ */
+
+package com.teragrep.cfe_16;
+
+import com.cloudbees.syslog.*;
+import com.teragrep.cfe_16.bo.HeaderInfo;
+import com.teragrep.cfe_16.bo.HttpEventData;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+
+import java.util.ArrayList;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Set;
+
+import static org.junit.Assert.*;
+
+/*
+ * Tests the functionality of Converter
+ */
+public class ConverterTests {
+
+ private Converter converter;
+ private HttpEventData eventData1;
+ private HttpEventData eventData2;
+ private HttpEventData eventData3;
+ private Severity supposedSeverity;
+ private Facility supposedFacility;
+ private SyslogMessage supposedSyslogMessage1;
+ private SyslogMessage supposedSyslogMessage2;
+ private SyslogMessage supposedSyslogMessage3;
+ private SDElement metadataSDE1;
+ private SDElement metadataSDE2;
+ private SDElement metadataSDE3;
+ private SyslogMessage returnedMessage1;
+ private SyslogMessage returnedMessage2;
+ private SyslogMessage returnedMessage3;
+ private Set returnedSDElements1;
+ private Set returnedSDElements2;
+ private Set returnedSDElements3;
+ private Set supposedSDElements1;
+ private Set supposedSDElements2;
+ private Set supposedSDElements3;
+
+ /*
+ * Initializes 3 HttpEventData objects and the data for them, 3 Structured Data
+ * Elements (SDE) with this data and 3 SyslogMessages with the help of those
+ * SDE's. We then call Converter's httpToSyslog() method to convert our
+ * HttpEventData objects so we have 3 supposed messages and 3 returned messages
+ * from Converter. SDElements from all of those messages are saved in Sets so
+ * that they can be compared to each other in the test methods.
+ */
+ @BeforeEach
+ public void initialize() {
+
+ converter = new Converter();
+
+ eventData1 = new HttpEventData();
+ eventData2 = new HttpEventData();
+ eventData3 = new HttpEventData();
+
+ supposedSyslogMessage1 = null;
+ supposedSyslogMessage2 = null;
+ supposedSyslogMessage3 = null;
+ metadataSDE1 = new SDElement("cfe_16-metadata@48577");
+ metadataSDE2 = new SDElement("cfe_16-metadata@48577");
+ metadataSDE3 = new SDElement("cfe_16-metadata@48577");
+
+ supposedSeverity = Severity.INFORMATIONAL;
+ supposedFacility = Facility.USER;
+
+ eventData1.setAuthenticationToken("AUTH_TOKEN_11111");
+ eventData1.setChannel("CHANNEL_11111");
+ eventData1.setAckID(0);
+ eventData1.setTimeSource("reported");
+ eventData1.setTime("1433188255253");
+ eventData1.setTimeParsed(true);
+ eventData1.setTimeAsLong(1433188255253L);
+ eventData1.setEvent("Event 1");
+
+ eventData2.setAuthenticationToken("AUTH_TOKEN_22222");
+ eventData2.setChannel("CHANNEL_22222");
+ eventData2.setAckID(1);
+ eventData2.setTimeSource("generated");
+ eventData2.setTimeParsed(false);
+ eventData2.setEvent("Event 2");
+
+ eventData3.setAuthenticationToken("AUTH_TOKEN_33333");
+ eventData3.setChannel("defaultchannel");
+ eventData3.setTimeSource("generated");
+ eventData3.setTimeParsed(false);
+ eventData3.setEvent("Event 3");
+
+ metadataSDE1.addSDParam("authentication_token", eventData1.getAuthenticationToken());
+ metadataSDE1.addSDParam("channel", eventData1.getChannel());
+ metadataSDE1.addSDParam("ack_id", String.valueOf(eventData1.getAckID()));
+ metadataSDE1.addSDParam("time_source", eventData1.getTimeSource());
+ metadataSDE1.addSDParam("time_parsed", "true");
+ metadataSDE1.addSDParam("time", eventData1.getTime());
+
+ metadataSDE2.addSDParam("authentication_token", eventData2.getAuthenticationToken());
+ metadataSDE2.addSDParam("channel", eventData2.getChannel());
+ metadataSDE2.addSDParam("ack_id", String.valueOf(eventData2.getAckID()));
+ metadataSDE2.addSDParam("time_source", eventData2.getTimeSource());
+
+ metadataSDE3.addSDParam("authentication_token", eventData3.getAuthenticationToken());
+ metadataSDE3.addSDParam("channel", eventData3.getChannel());
+ metadataSDE3.addSDParam("time_source", eventData3.getTimeSource());
+
+ supposedSyslogMessage1 = new SyslogMessage().withTimestamp(eventData1.getTimeAsLong())
+ .withSeverity(supposedSeverity).withAppName("capsulated").withHostname("cfe_16")
+ .withFacility(supposedFacility).withSDElement(metadataSDE1).withMsg(eventData1.getEvent());
+
+ supposedSyslogMessage2 = new SyslogMessage().withSeverity(supposedSeverity).withAppName("capsulated")
+ .withHostname("cfe_16").withFacility(supposedFacility).withSDElement(metadataSDE2)
+ .withMsg(eventData2.getEvent());
+
+ supposedSyslogMessage3 = new SyslogMessage().withSeverity(supposedSeverity).withAppName("capsulated")
+ .withHostname("cfe_16").withFacility(supposedFacility).withSDElement(metadataSDE3)
+ .withMsg(eventData3.getEvent());
+ HeaderInfo headerInfo = new HeaderInfo();
+
+ returnedMessage1 = converter.httpToSyslog(eventData1, headerInfo);
+ returnedMessage2 = converter.httpToSyslog(eventData2, headerInfo);
+ returnedMessage3 = converter.httpToSyslog(eventData3, headerInfo);
+
+ returnedSDElements1 = returnedMessage1.getSDElements();
+ returnedSDElements2 = returnedMessage2.getSDElements();
+ returnedSDElements3 = returnedMessage3.getSDElements();
+ supposedSDElements1 = supposedSyslogMessage1.getSDElements();
+ supposedSDElements2 = supposedSyslogMessage2.getSDElements();
+ supposedSDElements3 = supposedSyslogMessage3.getSDElements();
+
+ }
+
+ /*
+ * Compares the severity from the supposed SyslogMessage and the SyslogMessage
+ * returned from Converter. Severity should be hardcoded to INFORMATIONAL.
+ */
+ @Test
+ public void severityTest() {
+ assertEquals("Severity should be INFORMATIONAL", supposedSyslogMessage1.getSeverity(),
+ returnedMessage1.getSeverity());
+ assertEquals("Severity should be INFORMATIONAL", supposedSyslogMessage2.getSeverity(),
+ returnedMessage2.getSeverity());
+ assertEquals("Severity should be INFORMATIONAL", supposedSyslogMessage3.getSeverity(),
+ returnedMessage3.getSeverity());
+ }
+
+ /*
+ * Compares the facility from the supposed SyslogMessage and the SyslogMessage
+ * returned from Converter. Facility should be hardcoded to USER.
+ */
+ @Test
+ public void facilityTest() {
+ assertEquals("Facility should be USER", supposedSyslogMessage1.getFacility(), returnedMessage1.getFacility());
+ assertEquals("Facility should be USER", supposedSyslogMessage2.getFacility(), returnedMessage2.getFacility());
+ assertEquals("Facility should be USER", supposedSyslogMessage3.getFacility(), returnedMessage3.getFacility());
+ }
+
+ /*
+ * Compares the AppName and HostName from the supposed SyslogMessage and the
+ * SyslogMessage returned from Converter. AppName should be hardcoded to
+ * "capsulated" and HostName should be hardcoded to "cfe_16".
+ */
+ @Test
+ public void appNameAndHostNameTest() {
+ assertEquals("App name should be '" + supposedSyslogMessage1.getAppName() + "'",
+ supposedSyslogMessage1.getAppName(), returnedMessage1.getAppName());
+ assertEquals("App name should be '" + supposedSyslogMessage2.getAppName() + "'",
+ supposedSyslogMessage2.getAppName(), returnedMessage2.getAppName());
+ assertEquals("App name should be '" + supposedSyslogMessage3.getAppName() + "'",
+ supposedSyslogMessage3.getAppName(), returnedMessage3.getAppName());
+
+ assertEquals("Host name should be '" + supposedSyslogMessage1.getHostname() + "'",
+ supposedSyslogMessage1.getHostname(), returnedMessage1.getHostname());
+ assertEquals("Host name should be '" + supposedSyslogMessage1.getHostname() + "'",
+ supposedSyslogMessage2.getHostname(), returnedMessage2.getHostname());
+ assertEquals("Host name should be '" + supposedSyslogMessage1.getHostname() + "'",
+ supposedSyslogMessage3.getHostname(), returnedMessage3.getHostname());
+ }
+
+ /*
+ * Compares the msg from the supposed SyslogMessage and the SyslogMessage
+ * returned from Converter. msg should be the event from HttpEventData object.
+ */
+ @Test
+ public void msgTest() {
+ assertEquals("Msg should be '" + supposedSyslogMessage1.getMsg().toString() + "'",
+ supposedSyslogMessage1.getMsg().toString(), returnedMessage1.getMsg().toString());
+ assertEquals("Msg should be '" + supposedSyslogMessage2.getMsg().toString() + "'",
+ supposedSyslogMessage2.getMsg().toString(), returnedMessage2.getMsg().toString());
+ assertEquals("Msg should be '" + supposedSyslogMessage3.getMsg().toString() + "'",
+ supposedSyslogMessage3.getMsg().toString(), returnedMessage3.getMsg().toString());
+ }
+
+ /*
+ * Compares the time stamp from the supposed SyslogMessage and the SyslogMessage
+ * returned from Converter if the time stamp is assigned to the SyslogMessage.
+ * Otherwise time stamp should be null.
+ */
+ @Test
+ public void timestampTest() {
+
+ assertEquals("Timestamp should be: " + supposedSyslogMessage1.getTimestamp(),
+ supposedSyslogMessage1.getTimestamp(), returnedMessage1.getTimestamp());
+ assertNull("Timestamp should be null", supposedSyslogMessage2.getTimestamp());
+ assertNull("Timestamp should be null", supposedSyslogMessage3.getTimestamp());
+
+ }
+
+ /*
+ * Compares the SDElements from the supposed SyslogMessage and the SyslogMessage
+ * returned from Converter.
+ */
+ @Test
+ public void SDElementsTest() {
+
+ List supposedSDParams = new ArrayList();
+ List returnedSDParams = new ArrayList();
+ Iterator iterator;
+ SDElement returnedSDE = null;
+ SDElement supposedSDE = null;
+
+ // Gets the SDParams from the SDEs from the first SyslogMessage returned from
+ // Converter and saves them in a List
+ for (iterator = returnedSDElements1.iterator(); iterator.hasNext();) {
+ returnedSDE = iterator.next();
+ for (int i = 0; i < returnedSDE.getSdParams().size(); i++) {
+ returnedSDParams.add(returnedSDE.getSdParams().get(i));
+ }
+ }
+
+ // Gets the SDParams from the SDEs from the first SyslogMessage created in
+ // initialize() and saves them in a List
+ for (iterator = supposedSDElements1.iterator(); iterator.hasNext();) {
+ supposedSDE = iterator.next();
+ for (int i = 0; i < supposedSDE.getSdParams().size(); i++) {
+ supposedSDParams.add(supposedSDE.getSdParams().get(i));
+ }
+ }
+
+ // Goes through all the returned SDParams and checks that they are all found in
+ // supposed SDParams
+ for (int i = 0; i < returnedSDParams.size(); i++) {
+ assertTrue("SDParam '" + returnedSDParams.get(i) + "' should not be in returned SDElement.",
+ supposedSDParams.contains(returnedSDParams.get(i)));
+ }
+
+ // Goes through all supposed SDParams and checks that they are all found in
+ // returned SDParams
+ for (int i = 0; i < supposedSDParams.size(); i++) {
+ assertTrue("SDParam '" + supposedSDParams.get(i) + "' should be in returned SDElement.",
+ returnedSDParams.contains(supposedSDParams.get(i)));
+ }
+
+ // Create new empty ArrayList that we can save SDParams from the next SDElement
+ supposedSDParams = new ArrayList();
+ returnedSDParams = new ArrayList();
+
+ // Gets the SDParams from the SDEs from the second SyslogMessage returned from
+ // Converter and saves them in a List
+ for (iterator = returnedSDElements2.iterator(); iterator.hasNext();) {
+ returnedSDE = iterator.next();
+ for (int i = 0; i < returnedSDE.getSdParams().size(); i++)
+ returnedSDParams.add(returnedSDE.getSdParams().get(i));
+ }
+
+ // Gets the SDParams from the SDEs from the second SyslogMessage created in
+ // initialize() and saves them in a List
+ for (iterator = supposedSDElements2.iterator(); iterator.hasNext();) {
+ supposedSDE = iterator.next();
+ for (int i = 0; i < supposedSDE.getSdParams().size(); i++) {
+ supposedSDParams.add(supposedSDE.getSdParams().get(i));
+ }
+ }
+
+ // Goes through all the returned SDParams and checks that they are all found in
+ // supposed SDParams
+ for (int i = 0; i < returnedSDParams.size(); i++) {
+ assertTrue("SDParam '" + returnedSDParams.get(i) + "' should not be in returned SDElement.",
+ supposedSDParams.contains(returnedSDParams.get(i)));
+ }
+
+ // Goes through all supposed SDParams and checks that they are all found in
+ // returned SDParams
+ for (int i = 0; i < supposedSDParams.size(); i++) {
+ assertTrue("SDParam '" + supposedSDParams.get(i) + "' should be in returned SDElement.",
+ returnedSDParams.contains(supposedSDParams.get(i)));
+ }
+
+ // Create new empty ArrayList that we can save SDParams from the next SDElement
+ supposedSDParams = new ArrayList();
+ returnedSDParams = new ArrayList();
+
+ // Gets the SDParams from the SDEs from the third SyslogMessage returned from
+ // Converter and saves them in a List
+ for (iterator = returnedSDElements3.iterator(); iterator.hasNext();) {
+ returnedSDE = iterator.next();
+ for (int i = 0; i < returnedSDE.getSdParams().size(); i++)
+ returnedSDParams.add(returnedSDE.getSdParams().get(i));
+ }
+
+ // Gets the SDParams from the SDEs from the third SyslogMessage created in
+ // initialize() and saves them in a List
+ for (iterator = supposedSDElements3.iterator(); iterator.hasNext();) {
+ supposedSDE = iterator.next();
+ for (int i = 0; i < supposedSDE.getSdParams().size(); i++) {
+ supposedSDParams.add(supposedSDE.getSdParams().get(i));
+ }
+ }
+
+ // Goes through all the returned SDParams and checks that they are all found in
+ // supposed SDParams
+ for (int i = 0; i < returnedSDParams.size(); i++) {
+ assertTrue("SDParam '" + returnedSDParams.get(i) + "' should not be in returned SDElement.",
+ supposedSDParams.contains(returnedSDParams.get(i)));
+ }
+
+ // Goes through all supposed SDParams and checks that they are all found in
+ // returned SDParams
+ for (int i = 0; i < supposedSDParams.size(); i++) {
+ assertTrue("SDParam '" + supposedSDParams.get(i) + "' should be in returned SDElement.",
+ returnedSDParams.contains(supposedSDParams.get(i)));
+ }
+ }
+}
diff --git a/src/test/java/com/teragrep/cfe_16/SessionManagerTests.java b/src/test/java/com/teragrep/cfe_16/SessionManagerTests.java
new file mode 100644
index 0000000..86c4bb5
--- /dev/null
+++ b/src/test/java/com/teragrep/cfe_16/SessionManagerTests.java
@@ -0,0 +1,105 @@
+/*
+ * HTTP Event Capture to RFC5424 CFE_16
+ * Copyright (C) 2021 Suomen Kanuuna Oy
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see .
+ *
+ *
+ * Additional permission under GNU Affero General Public License version 3
+ * section 7
+ *
+ * If you modify this Program, or any covered work, by linking or combining it
+ * with other code, such other code is not for that reason alone subject to any
+ * of the requirements of the GNU Affero GPL version 3 as long as this Program
+ * is the same Program as licensed from Suomen Kanuuna Oy without any additional
+ * modifications.
+ *
+ * Supplemented terms under GNU Affero General Public License version 3
+ * section 7
+ *
+ * Origin of the software must be attributed to Suomen Kanuuna Oy. Any modified
+ * versions must be marked as "Modified version of" The Program.
+ *
+ * Names of the licensors and authors may not be used for publicity purposes.
+ *
+ * No rights are granted for use of trade names, trademarks, or service marks
+ * which are in The Program if any.
+ *
+ * Licensee must indemnify licensors and authors for any liability that these
+ * contractual assumptions impose on licensors and authors.
+ *
+ * To the extent this program is licensed as part of the Commercial versions of
+ * Teragrep, the applicable Commercial License may apply to this file if you as
+ * a licensee so wish it.
+ */
+
+package com.teragrep.cfe_16;
+
+import com.teragrep.cfe_16.bo.Session;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+
+import static org.junit.Assert.*;
+
+/*
+ * Tests the functionality of SessionManager
+ */
+public class SessionManagerTests {
+
+
+ private SessionManager sessionManager;
+
+ /*
+ * A SessionManager is initialized
+ */
+ @BeforeEach
+ public void initialize() {
+ sessionManager = new SessionManager();
+ }
+
+ /*
+ * Tests creating a session with SessionManager and getting that same session
+ * from SessionManager
+ */
+ @Test
+ public void createSessionAndGetItWithAuthTokenTest() {
+
+ String authToken1 = "AUTH_TOKEN_12345";
+ String authToken2 = "AUTH_TOKEN_54321";
+ String authToken3 = "AUTH_TOKEN_99999";
+
+ Session session1 = sessionManager.createSession(authToken1);
+ Session session2 = sessionManager.createSession(authToken2);
+ assertSame("Same session should be returned with the same authentication token", session1,
+ sessionManager.getSession(authToken1));
+ assertSame("Same session should be returned with the same authentication token", session2,
+ sessionManager.getSession(authToken2));
+ assertNotSame("Different session should be returned with a different authentication token", session1,
+ sessionManager.getSession(authToken2));
+ assertNotSame("Different session should be returned with a different authentication token", session2,
+ sessionManager.getSession(authToken1));
+ assertNull("Getting a session with an unused authentication token should return null",
+ sessionManager.getSession(authToken3));
+ }
+
+ @Test
+ public void sessionCreaationAndDeletionTests() {
+ Session session = sessionManager.createSession("AUTH");
+ assertTrue(session.addChannel(Session.DEFAULT_CHANNEL));
+ assertFalse(session.addChannel(Session.DEFAULT_CHANNEL));
+ assertTrue(session.doesChannelExist(Session.DEFAULT_CHANNEL));
+ assertTrue(session.removeChannel(Session.DEFAULT_CHANNEL));
+ assertTrue(!session.doesChannelExist(Session.DEFAULT_CHANNEL));
+ }
+}
diff --git a/src/test/java/com/teragrep/cfe_16/TokenManagerTests.java b/src/test/java/com/teragrep/cfe_16/TokenManagerTests.java
new file mode 100644
index 0000000..61fd1fd
--- /dev/null
+++ b/src/test/java/com/teragrep/cfe_16/TokenManagerTests.java
@@ -0,0 +1,113 @@
+/*
+ * HTTP Event Capture to RFC5424 CFE_16
+ * Copyright (C) 2021 Suomen Kanuuna Oy
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see .
+ *
+ *
+ * Additional permission under GNU Affero General Public License version 3
+ * section 7
+ *
+ * If you modify this Program, or any covered work, by linking or combining it
+ * with other code, such other code is not for that reason alone subject to any
+ * of the requirements of the GNU Affero GPL version 3 as long as this Program
+ * is the same Program as licensed from Suomen Kanuuna Oy without any additional
+ * modifications.
+ *
+ * Supplemented terms under GNU Affero General Public License version 3
+ * section 7
+ *
+ * Origin of the software must be attributed to Suomen Kanuuna Oy. Any modified
+ * versions must be marked as "Modified version of" The Program.
+ *
+ * Names of the licensors and authors may not be used for publicity purposes.
+ *
+ * No rights are granted for use of trade names, trademarks, or service marks
+ * which are in The Program if any.
+ *
+ * Licensee must indemnify licensors and authors for any liability that these
+ * contractual assumptions impose on licensors and authors.
+ *
+ * To the extent this program is licensed as part of the Commercial versions of
+ * Teragrep, the applicable Commercial License may apply to this file if you as
+ * a licensee so wish it.
+ */
+
+package com.teragrep.cfe_16;
+
+import org.junit.jupiter.api.Test;
+import org.springframework.mock.web.MockHttpServletRequest;
+
+import java.util.Base64;
+
+import static org.junit.Assert.*;
+
+/*
+ * Tests the functionality of TokenManager
+ */
+public class TokenManagerTests {
+
+ TokenManager manager = new TokenManager();
+
+ /*
+ * Tests TokenManager's tokenIsMissing() method which checks if
+ * HttpServletRequest's header has an authentication token in it.
+ */
+ @Test
+ public void tokenCheckingTest() {
+
+ MockHttpServletRequest requestWithHttpHeaderAuth = new MockHttpServletRequest();
+ MockHttpServletRequest requestWithBasicAuth = new MockHttpServletRequest();
+ MockHttpServletRequest requestWithoutAuth = new MockHttpServletRequest();
+ String authToken = "AUTH_TOKEN_11111";
+
+ requestWithHttpHeaderAuth.addHeader("Authorization", authToken);
+ requestWithBasicAuth.addHeader("Authorization", "Basic x:" + authToken);
+
+ assertFalse("Token should be found from the request", manager.tokenIsMissing(requestWithHttpHeaderAuth));
+ assertFalse("Token should be found from the request", manager.tokenIsMissing(requestWithBasicAuth));
+ assertTrue("Token should not be found from the request", manager.tokenIsMissing(requestWithoutAuth));
+ }
+
+ /*
+ * Tests TokenManager's isTokenInBasic() method which checks if the
+ * HttpServletRequest's header's authentication token is in basic authentication
+ * format.
+ */
+ @Test
+ public void basicAuthCheckingTest() {
+ String authToken = "AUTH_TOKEN_11111";
+ String basicAuthHeader = "Basic x:" + authToken;
+
+ assertTrue("Authorization header should be in basic format", manager.isTokenInBasic(basicAuthHeader));
+ assertFalse("Authorization should not be in basic format when querying with only the authentication token.",
+ manager.isTokenInBasic(authToken));
+ }
+
+ /*
+ * Tests TokenManager's getTokenFromBasic() method which returns the
+ * authentication token when it is given in basic authentication format.
+ */
+ @Test
+ public void getTokenFromBasicAuthTest() {
+ String authToken = "AUTH_TOKEN_11111";
+ String basicAuthCredentials = "x:" + authToken;
+ String credentialsEncoded = Base64.getEncoder().encodeToString(basicAuthCredentials.getBytes());
+ String basicAuthHeader = "Basic " + credentialsEncoded;
+
+ assertEquals("Method should return the authentication token extracted from the Basic Authentication format",
+ authToken, manager.getTokenFromBasic(basicAuthHeader));
+ }
+
+}
diff --git a/src/test/java/com/teragrep/cfe_16/it/AckManagerIT.java b/src/test/java/com/teragrep/cfe_16/it/AckManagerIT.java
new file mode 100644
index 0000000..62ca657
--- /dev/null
+++ b/src/test/java/com/teragrep/cfe_16/it/AckManagerIT.java
@@ -0,0 +1,311 @@
+/*
+ * HTTP Event Capture to RFC5424 CFE_16
+ * Copyright (C) 2021 Suomen Kanuuna Oy
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see .
+ *
+ *
+ * Additional permission under GNU Affero General Public License version 3
+ * section 7
+ *
+ * If you modify this Program, or any covered work, by linking or combining it
+ * with other code, such other code is not for that reason alone subject to any
+ * of the requirements of the GNU Affero GPL version 3 as long as this Program
+ * is the same Program as licensed from Suomen Kanuuna Oy without any additional
+ * modifications.
+ *
+ * Supplemented terms under GNU Affero General Public License version 3
+ * section 7
+ *
+ * Origin of the software must be attributed to Suomen Kanuuna Oy. Any modified
+ * versions must be marked as "Modified version of" The Program.
+ *
+ * Names of the licensors and authors may not be used for publicity purposes.
+ *
+ * No rights are granted for use of trade names, trademarks, or service marks
+ * which are in The Program if any.
+ *
+ * Licensee must indemnify licensors and authors for any liability that these
+ * contractual assumptions impose on licensors and authors.
+ *
+ * To the extent this program is licensed as part of the Commercial versions of
+ * Teragrep, the applicable Commercial License may apply to this file if you as
+ * a licensee so wish it.
+ */
+
+package com.teragrep.cfe_16.it;
+
+import com.fasterxml.jackson.databind.JsonNode;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.teragrep.cfe_16.AckManager;
+import com.teragrep.cfe_16.bo.Ack;
+import com.teragrep.cfe_16.bo.Session;
+import com.teragrep.cfe_16.config.Configuration;
+import com.teragrep.cfe_16.exceptionhandling.ServerIsBusyException;
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.boot.test.context.SpringBootTest;
+import org.springframework.test.annotation.DirtiesContext;
+import org.springframework.test.annotation.DirtiesContext.ClassMode;
+import org.springframework.test.context.TestPropertySource;
+
+import java.io.IOException;
+import java.util.Map;
+
+import static org.junit.Assert.*;
+
+/*
+ * Tests the functionality of ackManager
+ */
+@SpringBootTest
+@DirtiesContext(classMode = ClassMode.AFTER_EACH_TEST_METHOD)
+@TestPropertySource(properties = {
+ "syslog.server.host=127.0.0.1",
+ "syslog.server.port=1234",
+ "syslog.server.protocol=TCP",
+ "max.channels=1000000",
+ "max.ack.value=1000000",
+ "max.ack.age=20000",
+ "max.session.age=30000",
+ "poll.time=30000",
+ "spring.devtools.add-properties=false",
+ "server.print.times=true"
+ })
+public class AckManagerIT {
+ private static final Logger LOGGER = LoggerFactory.getLogger(AckManagerIT.class);
+ private String authToken1;
+ private String authToken2;
+
+ private String channel1;
+ private String channel2;
+
+ @Autowired
+ private Configuration configuration;
+ @Autowired
+ private AckManager ackManager;
+ /*
+ * Initializes 2 channels. getCurrentAckValue in channel 1 is called 3 times
+ * which means that the next Ack id given to a sent event should be 3.
+ */
+ @BeforeEach
+ public void initialize() {
+
+ authToken1 = "AUTH_TOKEN_11111";
+ authToken2 = "AUTH_TOKEN_22222";
+
+ channel1 = "CHANNEL_11111";
+ channel2 = "CHANNEL_22222";
+
+ }
+
+ /*
+ * In initialize() we call channel 1's getCurrentAckValue 3 times, so the next
+ * time we call it, the currentAckValue() should return 3 and increase the ack
+ * value by 1, so the next time it will be 4. We have not called
+ * getCurrentAckValue() in channel2 yet, so it should be 0.
+ */
+ @Test
+ public void getCurrentAckValueTest() {
+ int currentAckValue;
+ currentAckValue = ackManager.getCurrentAckValue(this.authToken1, this.channel1);
+ ackManager.incrementAckValue(this.authToken1, this.channel1);
+
+ currentAckValue = ackManager.getCurrentAckValue(this.authToken1, this.channel1);
+ ackManager.incrementAckValue(this.authToken1, this.channel1);
+
+ currentAckValue = ackManager.getCurrentAckValue(this.authToken1, this.channel1);
+ ackManager.incrementAckValue(this.authToken1, this.channel1);
+
+ currentAckValue = ackManager.getCurrentAckValue(this.authToken1, this.channel1);
+ ackManager.incrementAckValue(this.authToken1, this.channel1);
+
+ assertEquals("channel1 current ack value should be 3", 3, currentAckValue);
+
+ currentAckValue = ackManager.getCurrentAckValue(this.authToken1, this.channel1);
+ ackManager.incrementAckValue(this.authToken1, this.channel1);
+ assertEquals("channel1 current ack value should be 4", 4, currentAckValue);
+
+ currentAckValue = ackManager.getCurrentAckValue(this.authToken2, this.channel2);
+ assertEquals("channel2 current ack value should be 0", 0, currentAckValue);
+ }
+
+ /*
+ * First we acknowledge the Ack with an id of 0 in channel 1, then we test that
+ * it is indeed acknowledged. All the other Acks should not be acknowledged. If
+ * Ack id is not used at all, isAckAcknowledged should return false.
+ */
+ @Test
+ public void acknowledgeTest() {
+ ackManager.initializeContext(this.authToken1, this.channel1);
+ ackManager.addAck(this.authToken1, this.channel1, new Ack(0, false));
+ ackManager.acknowledge(this.authToken1, this.channel1, 0);
+ assertTrue("ackId 0 should be acknowledged for channel 1", ackManager.isAckAcknowledged(this.authToken1, this.channel1, 0));
+ assertFalse("ackId 1 should not be acknowledged for channel 1", ackManager.isAckAcknowledged(this.authToken1, this.channel1, 1));
+ assertFalse("ackId 10 is not used yet for channel 1, so isAckAcknowledged should return false",
+ ackManager.isAckAcknowledged(this.authToken1, this.channel1, 10));
+ ackManager.incrementAckValue(this.authToken1, this.channel1);
+ ackManager.initializeContext(this.authToken2, this.channel2);
+ assertFalse("ackId 0 is not used yet for channel 2 so isAckAcknowledged should return false",
+ ackManager.isAckAcknowledged(this.authToken2, this.channel2, 0));
+ }
+
+ /*
+ * Tests getting the Ack statuses from ackManager. First we create the request
+ * bodies and the supposed responses as strings. Then we create the nodes for
+ * the requests and read the strings into the node object. After that
+ * ackManager's getRequestedAckStatuses() is called with the JsonNode requests
+ * and the response is compared to the supposed responses.
+ */
+ @Test
+ public void getRequestedAckStatusesTest() {
+ String requestAsString = "{\"acks\": [1,3,4]}";
+ String notIntRequestAsString = "{\"acks\": [\"a\",\"b\",\"c\"]}";
+ String faultyRequestAsString = "{\"test\": [1,3,4]}";
+ String supposedResponseAsStringOneTrue = "{\"1\":true,\"3\":false,\"4\":false}";
+ String supposedResponseAsStringAllFalse = "{\"1\":false,\"3\":false,\"4\":false}";
+ ObjectMapper mapper = new ObjectMapper();
+ JsonNode emptyJsonNode = mapper.createObjectNode();
+ JsonNode queryNode = mapper.createObjectNode();
+ JsonNode faultyNode = mapper.createObjectNode();
+ JsonNode notIntNode = mapper.createObjectNode();
+ ackManager.initializeContext(this.authToken1, this.channel1);
+ assertTrue(ackManager.addAck(this.authToken1, this.channel1, new Ack(1, false)));
+ assertTrue(ackManager.acknowledge(this.authToken1, this.channel1, 1));
+ try {
+ queryNode = mapper.readTree(requestAsString);
+ faultyNode = mapper.readTree(faultyRequestAsString);
+ notIntNode = mapper.readTree(notIntRequestAsString);
+ } catch (IOException e) {
+ throw new RuntimeException(e);
+ }
+ assertEquals("getRequestedAckStatuses should return null, when providing a null value as a parameter",
+ emptyJsonNode, ackManager.getRequestedAckStatuses(this.authToken1, "", null));
+
+ try {
+ assertEquals("ackId 1 status should be true on channel1, others should be false.",
+ supposedResponseAsStringOneTrue, ackManager.getRequestedAckStatuses(this.authToken1, this.channel1, queryNode).toString());
+ } catch (Throwable e1) {
+ // TODO Auto-generated catch block
+ LOGGER.warn("Failed to handle ack request: ", e1);
+ }
+
+ assertEquals(
+ "ackId 1 status should be false on channel1 after requesting it's status once. All others should be false as well",
+ supposedResponseAsStringAllFalse, ackManager.getRequestedAckStatuses(this.authToken1, this.channel1, queryNode).toString());
+
+ ackManager.initializeContext(this.authToken2, this.channel2);
+ assertEquals("All ack statuses should be false for channel2", supposedResponseAsStringAllFalse,
+ ackManager.getRequestedAckStatuses(this.authToken2, this.channel2, queryNode).toString());
+
+ assertEquals("An empty JsonNode should be returned when querying with an empty JsonNode", emptyJsonNode,
+ ackManager.getRequestedAckStatuses(this.authToken1, this.channel1, emptyJsonNode));
+
+ assertEquals(
+ "An empty JsonNode should be returned when querying with a JsonNode that has no \"acks\" field in it.",
+ emptyJsonNode, ackManager.getRequestedAckStatuses(this.authToken1, this.channel1, faultyNode));
+ try {
+ assertEquals(
+ "An empty JsonNode should be returned when querying with a JsonNode that has \"acks\" field in it, but it has something else than integers as it's values",
+ emptyJsonNode, ackManager.getRequestedAckStatuses(this.authToken1, this.channel1, notIntNode));
+ fail("Expected IllegalArgumentException");
+ } catch (IllegalArgumentException e) {
+ }
+ }
+
+ public void getCurrentAckValueAndIncrementTest() {
+ AckManager ackManager1 = new AckManager();
+ AckManager ackManager2 = new AckManager();
+
+ assertEquals("AckManager 1 should return 0", 0, ackManager1.getCurrentAckValue(this.authToken1, Session.DEFAULT_CHANNEL));
+ ackManager1.incrementAckValue(this.authToken1, Session.DEFAULT_CHANNEL);
+ assertEquals("AckManager 1 should return 1", 1, ackManager1.getCurrentAckValue(this.authToken1, Session.DEFAULT_CHANNEL));
+
+ assertEquals("AckManager 2 should return 0", 0, ackManager2.getCurrentAckValue(this.authToken1, Session.DEFAULT_CHANNEL));
+ ackManager2.incrementAckValue(this.authToken1, Session.DEFAULT_CHANNEL);
+ assertEquals("AckManager 2 should return 1", 1, ackManager2.getCurrentAckValue(this.authToken1, Session.DEFAULT_CHANNEL));
+
+ }
+
+ /*
+ * Tests deleting the Ack from ackManager. First we get the list that is
+ * currently in the ackManager and save it to list1 variable. After that we
+ * delete an Ack from the ackManager's list, then we get the list from
+ * ackManager again and save it to list2 variable. These 2 lists are then
+ * compared to each other.
+ */
+ @Test
+ public void deleteAckTest() {
+ AckManager ackManager1 = new AckManager();
+ AckManager ackManager2 = new AckManager();
+
+ ackManager1.initializeContext(this.authToken1, this.channel1);
+ assertTrue(ackManager1.addAck(this.authToken1, this.channel1, new Ack(0, false)));
+ assertTrue(ackManager1.addAck(this.authToken1, this.channel1, new Ack(1, false)));
+
+ Map list1 = ackManager1.getAckList(this.authToken1, this.channel1);
+ int list1Size = ackManager1.getAckListSize(this.authToken1, this.channel1);
+ assertEquals("Ack list 1 size should be 2.", 2, list1Size);
+
+ Ack deletedAck = list1.values().iterator().next();
+
+ ackManager2.initializeContext(this.authToken2, this.channel2);
+ assertTrue(ackManager2.addAck(this.authToken2, this.channel2, new Ack(0, false)));
+
+ ackManager2.initializeContext(this.authToken2, this.channel2);
+ assertTrue(ackManager2.addAck(this.authToken2, this.channel2, new Ack(1, false)));
+
+ ackManager2.deleteAckFromList(this.authToken2, this.channel2, deletedAck);
+ Map list2 = ackManager2.getAckList(this.authToken2, this.channel2);
+ int list2Size = list2.size();
+
+ assertNotSame("Ack lists should not be same", list1.toString(), list2.toString());
+ assertEquals("list2 should be shorter by one index", list1Size - 1, list2Size);
+ assertFalse("list2 should not contain the deleted ack", list2.containsKey(deletedAck.getId()));
+ }
+
+ /*
+ * Max Ack value is set to 2, so the Ack list should be full after
+ * getCurrentAckValue() is called 3 times. getCurrentAckValue is called 4 times
+ * here, so ServerIsBusyException is expected to happen.
+ */
+ @Test
+ public void maxAckValueTest() {
+ Assertions.assertThrows(ServerIsBusyException.class, () -> {
+ /* AckManager ackManager = new AckManager(); */
+
+ this.configuration.setMaxAckValue(2);
+ int ackId;
+
+ ackId = ackManager.getCurrentAckValue(this.authToken1, this.channel1);
+ ackManager.incrementAckValue(this.authToken1, this.channel1);
+ ackManager.addAck(this.authToken1, this.channel1, new Ack(ackId, false));
+
+ ackId = ackManager.getCurrentAckValue(this.authToken1, this.channel1);
+ ackManager.incrementAckValue(this.authToken1, this.channel1);
+ ackManager.addAck(this.authToken1, this.channel1, new Ack(ackId, false));
+
+ ackId = ackManager.getCurrentAckValue(this.authToken1, this.channel1);
+ ackManager.incrementAckValue(this.authToken1, this.channel1);
+ ackManager.addAck(this.authToken1, this.channel1, new Ack(ackId, false));
+
+ ackId = ackManager.getCurrentAckValue(this.authToken1, this.channel1);
+ ackManager.incrementAckValue(this.authToken1, this.channel1);
+ ackManager.addAck(this.authToken1, this.channel1, new Ack(ackId, false));
+ });
+ }
+}
diff --git a/src/test/java/com/teragrep/cfe_16/it/ConfigurationIT.java b/src/test/java/com/teragrep/cfe_16/it/ConfigurationIT.java
new file mode 100644
index 0000000..2895b70
--- /dev/null
+++ b/src/test/java/com/teragrep/cfe_16/it/ConfigurationIT.java
@@ -0,0 +1,104 @@
+/*
+ * HTTP Event Capture to RFC5424 CFE_16
+ * Copyright (C) 2021 Suomen Kanuuna Oy
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see .
+ *
+ *
+ * Additional permission under GNU Affero General Public License version 3
+ * section 7
+ *
+ * If you modify this Program, or any covered work, by linking or combining it
+ * with other code, such other code is not for that reason alone subject to any
+ * of the requirements of the GNU Affero GPL version 3 as long as this Program
+ * is the same Program as licensed from Suomen Kanuuna Oy without any additional
+ * modifications.
+ *
+ * Supplemented terms under GNU Affero General Public License version 3
+ * section 7
+ *
+ * Origin of the software must be attributed to Suomen Kanuuna Oy. Any modified
+ * versions must be marked as "Modified version of" The Program.
+ *
+ * Names of the licensors and authors may not be used for publicity purposes.
+ *
+ * No rights are granted for use of trade names, trademarks, or service marks
+ * which are in The Program if any.
+ *
+ * Licensee must indemnify licensors and authors for any liability that these
+ * contractual assumptions impose on licensors and authors.
+ *
+ * To the extent this program is licensed as part of the Commercial versions of
+ * Teragrep, the applicable Commercial License may apply to this file if you as
+ * a licensee so wish it.
+ */
+
+package com.teragrep.cfe_16.it;
+
+
+import com.teragrep.cfe_16.config.Configuration;
+import com.teragrep.rlp_03.Server;
+import com.teragrep.rlp_03.ServerFactory;
+import com.teragrep.rlp_03.config.Config;
+import com.teragrep.rlp_03.delegate.DefaultFrameDelegate;
+import com.teragrep.rlp_03.delegate.FrameDelegate;
+import org.junit.jupiter.api.AfterAll;
+import org.junit.jupiter.api.BeforeAll;
+import org.junit.jupiter.api.Test;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.boot.test.context.SpringBootTest;
+
+import java.io.IOException;
+import java.util.function.Supplier;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+
+@SpringBootTest
+public class ConfigurationIT {
+ private static final Logger LOGGER = LoggerFactory.getLogger(ConfigurationIT.class);
+ @Autowired
+ private Configuration configuration;
+
+ private static Server server;
+ private static final String hostname = "localhost";
+ private static Integer port = 1235;
+ @BeforeAll
+ public static void init() throws IOException, InterruptedException {
+ Supplier frameDelegateSupplier = () -> new DefaultFrameDelegate((frame) -> LOGGER.debug(frame.relpFrame().payload().toString()));
+ Config config = new Config(port, 1);
+ ServerFactory serverFactory = new ServerFactory(config, frameDelegateSupplier);
+
+ server = serverFactory.create();
+ Thread serverThread = new Thread(server);
+ serverThread.start();
+ server.startup.waitForCompletion();
+ }
+
+ @AfterAll
+ public static void cleanup() throws InterruptedException {
+ server.stop();
+ }
+
+ @Test
+ public void instantiateConfigurationTest() {
+ String expected =
+ "Configuration [sysLogHost=127.0.0.1, sysLogProtocol=relp, sysLogPort=1235, maxAckValue=1000000, maxAckAge=20000, maxSessionAge=30000, "
+ + "maxChannels=1000000, pollTime=1000000, printTimes=true]";
+ LOGGER.debug(configuration.toString());
+
+ assertEquals(expected, configuration.toString());
+ }
+}
diff --git a/src/test/java/com/teragrep/cfe_16/it/SendEventsIT.java b/src/test/java/com/teragrep/cfe_16/it/SendEventsIT.java
new file mode 100644
index 0000000..7ab9136
--- /dev/null
+++ b/src/test/java/com/teragrep/cfe_16/it/SendEventsIT.java
@@ -0,0 +1,182 @@
+/*
+ * HTTP Event Capture to RFC5424 CFE_16
+ * Copyright (C) 2021 Suomen Kanuuna Oy
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see .
+ *
+ *
+ * Additional permission under GNU Affero General Public License version 3
+ * section 7
+ *
+ * If you modify this Program, or any covered work, by linking or combining it
+ * with other code, such other code is not for that reason alone subject to any
+ * of the requirements of the GNU Affero GPL version 3 as long as this Program
+ * is the same Program as licensed from Suomen Kanuuna Oy without any additional
+ * modifications.
+ *
+ * Supplemented terms under GNU Affero General Public License version 3
+ * section 7
+ *
+ * Origin of the software must be attributed to Suomen Kanuuna Oy. Any modified
+ * versions must be marked as "Modified version of" The Program.
+ *
+ * Names of the licensors and authors may not be used for publicity purposes.
+ *
+ * No rights are granted for use of trade names, trademarks, or service marks
+ * which are in The Program if any.
+ *
+ * Licensee must indemnify licensors and authors for any liability that these
+ * contractual assumptions impose on licensors and authors.
+ *
+ * To the extent this program is licensed as part of the Commercial versions of
+ * Teragrep, the applicable Commercial License may apply to this file if you as
+ * a licensee so wish it.
+ */
+
+package com.teragrep.cfe_16.it;
+
+import com.teragrep.cfe_16.service.HECService;
+import org.junit.jupiter.api.*;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.boot.test.context.SpringBootTest;
+import org.springframework.mock.web.MockHttpServletRequest;
+import org.springframework.test.context.TestPropertySource;
+
+import java.io.BufferedReader;
+import java.io.IOException;
+import java.io.InputStreamReader;
+import java.io.InterruptedIOException;
+import java.net.ServerSocket;
+import java.net.Socket;
+import java.nio.channels.Selector;
+import java.time.Duration;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.concurrent.*;
+import java.util.concurrent.atomic.AtomicInteger;
+
+@TestPropertySource(properties = {
+ "syslog.server.host=127.0.0.1",
+ "syslog.server.port=1236",
+ "syslog.server.protocol=TCP",
+ "max.channels=1000000",
+ "max.ack.value=1000000",
+ "max.ack.age=20000",
+ "max.session.age=30000",
+ "poll.time=30000",
+ "spring.devtools.add-properties=false",
+ "server.print.times=true"
+ })
+@SpringBootTest
+public class SendEventsIT implements Runnable {
+ @Autowired
+ private HECService service;
+
+ private ServerSocket serverSocket;
+ private Thread thread;
+ private AtomicInteger numberOfRequestsMade;
+
+ private MockHttpServletRequest request1;
+ private String eventInJson;
+ private String channel1;
+ private Selector selector;
+ private final static int SERVER_PORT = 1236;
+ private CountDownLatch countDownLatch = new CountDownLatch(0);
+
+ @BeforeEach
+ public void init() throws IOException {
+ this.thread = new Thread(this);
+ this.numberOfRequestsMade = new AtomicInteger(0);
+ this.serverSocket = new ServerSocket(SERVER_PORT);
+ this.selector = Selector.open();
+
+ this.request1 = new MockHttpServletRequest();
+ this.request1.addHeader("Authorization", "AUTH_TOKEN_11111");
+ this.channel1 = "CHANNEL_11111";
+ this.eventInJson = "{\"sourcetype\":\"access\", \"source\":\"/var/log/access.log\", \"event\": {\"message\":\"Access log test message 1\"}} {\"sourcetype\":\"access\", \"source\":\"/var/log/access.log\", \"event\": {\"message\":\"Access log test message 2\"}}";
+
+ this.thread.start();
+ }
+
+ @AfterEach
+ public void shutdown() {
+ this.thread.interrupt();
+ }
+
+ public void run() {
+ while (true) {
+ try {
+ Socket socket = this.serverSocket.accept();
+ BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(socket.getInputStream()));
+ String line;
+ while ((line = bufferedReader.readLine()) != null) {
+ this.numberOfRequestsMade.getAndIncrement();
+ countDownLatch.countDown();
+ }
+ bufferedReader.close();
+ socket.close();
+ if (this.numberOfRequestsMade.get() % 5 == 0) {
+ this.serverSocket.close();
+ this.serverSocket = new ServerSocket(SERVER_PORT);
+ }
+ } catch (InterruptedIOException e) {
+ break;
+ } catch (IOException e) {
+ throw new RuntimeException(e);
+ }
+ }
+ }
+
+ @Test
+ public void sendEventsTest() throws IOException, InterruptedException, ExecutionException {
+ int NUMBER_OF_EVENTS_TO_BE_SENT = 100;
+ countDownLatch = new CountDownLatch(NUMBER_OF_EVENTS_TO_BE_SENT);
+ ExecutorService es = Executors.newFixedThreadPool(8);
+ List > futures = new ArrayList<>();
+
+ for (int i = 0; i < NUMBER_OF_EVENTS_TO_BE_SENT; i++) {
+ CompletableFuture f = CompletableFuture.supplyAsync(() -> service.sendEvents(request1, channel1, eventInJson).toString());
+ futures.add(f);
+ }
+ List supposedResponses = new ArrayList();
+ for (int i = 0; i < NUMBER_OF_EVENTS_TO_BE_SENT; i++) {
+ final String supposedResponse = "{\"text\":\"Success\",\"code\":0,\"ackID\":" + i + "}";
+ supposedResponses.add(supposedResponse);
+ }
+ int countFuture = 0;
+ for (Future f : futures) {
+ final String actualResponse = f.get();
+ Assertions.assertTrue(supposedResponses.contains(actualResponse), "Service should return JSON object with fields 'text', 'code' and 'ackID' (ackID should be " + countFuture + ")");
+ countFuture++;
+ }
+ countDownLatch.await(1, TimeUnit.SECONDS);
+ Assertions.assertTimeoutPreemptively(Duration.ofSeconds(5), () -> {
+ while(NUMBER_OF_EVENTS_TO_BE_SENT * 2 != this.numberOfRequestsMade.get()) {
+ Thread.sleep(500);
+ }
+ });
+ es.shutdownNow();
+ }
+
+ @Disabled
+ @Test
+ public void send1EventTest() throws IOException, InterruptedException {
+ countDownLatch = new CountDownLatch(1);
+ String supposedResponse = "{\"text\":\"Success\",\"code\":0,\"ackID\":" + 0 + "}";
+ Assertions.assertEquals(service.sendEvents(request1, channel1, eventInJson).toString(), supposedResponse, "Service should return JSON object with fields 'text', 'code' and 'ackID' (ackID should be " + 0 + ")");
+
+ countDownLatch.await(5, TimeUnit.SECONDS);
+ Assertions.assertEquals(2, this.numberOfRequestsMade, "Number of events received should match the number of sent ones");
+ }
+}
diff --git a/src/test/java/com/teragrep/cfe_16/it/ServiceAndEventManagerIT.java b/src/test/java/com/teragrep/cfe_16/it/ServiceAndEventManagerIT.java
new file mode 100644
index 0000000..1192523
--- /dev/null
+++ b/src/test/java/com/teragrep/cfe_16/it/ServiceAndEventManagerIT.java
@@ -0,0 +1,525 @@
+/*
+ * HTTP Event Capture to RFC5424 CFE_16
+ * Copyright (C) 2021 Suomen Kanuuna Oy
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see .
+ *
+ *
+ * Additional permission under GNU Affero General Public License version 3
+ * section 7
+ *
+ * If you modify this Program, or any covered work, by linking or combining it
+ * with other code, such other code is not for that reason alone subject to any
+ * of the requirements of the GNU Affero GPL version 3 as long as this Program
+ * is the same Program as licensed from Suomen Kanuuna Oy without any additional
+ * modifications.
+ *
+ * Supplemented terms under GNU Affero General Public License version 3
+ * section 7
+ *
+ * Origin of the software must be attributed to Suomen Kanuuna Oy. Any modified
+ * versions must be marked as "Modified version of" The Program.
+ *
+ * Names of the licensors and authors may not be used for publicity purposes.
+ *
+ * No rights are granted for use of trade names, trademarks, or service marks
+ * which are in The Program if any.
+ *
+ * Licensee must indemnify licensors and authors for any liability that these
+ * contractual assumptions impose on licensors and authors.
+ *
+ * To the extent this program is licensed as part of the Commercial versions of
+ * Teragrep, the applicable Commercial License may apply to this file if you as
+ * a licensee so wish it.
+ */
+
+package com.teragrep.cfe_16.it;
+
+import com.fasterxml.jackson.databind.JsonNode;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.teragrep.cfe_16.AckManager;
+import com.teragrep.cfe_16.EventManager;
+import com.teragrep.cfe_16.bo.HeaderInfo;
+import com.teragrep.cfe_16.bo.HttpEventData;
+import com.teragrep.cfe_16.bo.Session;
+import com.teragrep.cfe_16.exceptionhandling.*;
+import com.teragrep.cfe_16.service.HECService;
+import com.teragrep.rlp_03.Server;
+import com.teragrep.rlp_03.ServerFactory;
+import com.teragrep.rlp_03.config.Config;
+import com.teragrep.rlp_03.delegate.DefaultFrameDelegate;
+import com.teragrep.rlp_03.delegate.FrameDelegate;
+import org.junit.jupiter.api.*;
+import org.powermock.reflect.Whitebox;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.boot.test.context.SpringBootTest;
+import org.springframework.mock.web.MockHttpServletRequest;
+import org.springframework.test.context.TestPropertySource;
+
+import java.io.IOException;
+import java.net.ServerSocket;
+import java.util.function.Supplier;
+
+import static org.junit.Assert.*;
+
+/*
+ * Tests the functionality of HECServiceImpl
+ */
+
+@SpringBootTest
+@TestPropertySource(properties = {
+ "syslog.server.host=127.0.0.1",
+ "syslog.server.port=1601",
+ "syslog.server.protocol=RELP",
+ "max.channels=1000000",
+ "max.ack.value=1000000",
+ "max.ack.age=20000",
+ "max.session.age=30000",
+ "poll.time=30000",
+ "spring.devtools.add-properties=false",
+ "server.print.times=true"
+ })
+public class ServiceAndEventManagerIT {
+ private static final Logger LOGGER = LoggerFactory.getLogger(ServiceAndEventManagerIT.class);
+ private static Server server;
+ private static final String hostname = "localhost";
+ private static Integer port = 1601;
+ @BeforeAll
+ public static void init_x() throws IOException, InterruptedException {
+ Supplier frameDelegateSupplier = () -> new DefaultFrameDelegate((frame) -> LOGGER.debug(frame.relpFrame().payload().toString()));
+ Config config = new Config(port, 1);
+ ServerFactory serverFactory = new ServerFactory(config, frameDelegateSupplier);
+
+ server = serverFactory.create();
+ Thread serverThread = new Thread(server);
+ serverThread.start();
+ server.startup.waitForCompletion();
+ }
+
+ @AfterAll
+ public static void cleanup() throws InterruptedException {
+ server.stop();
+ }
+ @Autowired
+ private HECService service;
+ @Autowired
+ private AckManager ackManager;
+ private static final ServerSocket serverSocket = getSocket();
+
+ private MockHttpServletRequest request1;
+ private MockHttpServletRequest request2;
+ private MockHttpServletRequest request3;
+ private MockHttpServletRequest request4;
+ private MockHttpServletRequest request5;
+
+ private String eventInJson;
+ private String channel1;
+ private String channel2;
+ private String channel3;
+ private String defaultChannel;
+ private String authToken1;
+ private String authToken2;
+ private String authToken3;
+ private String authToken4;
+
+ private String ackRequest;
+ private ObjectMapper objectMapper;
+ private JsonNode ackRequestNode;
+
+ @Autowired
+ private EventManager eventManager;
+
+ private HeaderInfo headerInfo = new HeaderInfo();
+
+ private static ServerSocket getSocket() {
+ ServerSocket socket = null;
+ try {
+ socket = new ServerSocket(1234);
+ } catch (IOException e) {
+ LOGGER.warn("Could not get a server socket: ", e);
+ throw new RuntimeException(e);
+ }
+ return socket;
+ }
+
+ @AfterAll
+ public static void closeServerSocket() {
+ try {
+ serverSocket.close();
+ } catch (IOException e) {
+ LOGGER.warn("Could not close server socket: ", e);
+ }
+ }
+
+ /*
+ * Opens a ServerSocket so that the sending an event won't produce an error. 4
+ * Mock requests are created. request2 will not have authorization token
+ * assigned to it. eventInJson variable is the body of the request as a string
+ * when sending an event and ackRequest is the body of the request when
+ * requesting Ack statuses.
+ */
+ @BeforeEach
+ public void initialize() {
+ /*
+ * try { serverSocket = new ServerSocket(props.getSyslogPort()); } catch
+ * (IOException e) { e.printStackTrace(); }
+ */
+
+ objectMapper = new ObjectMapper();
+ request1 = new MockHttpServletRequest();
+ request2 = new MockHttpServletRequest();
+ request3 = new MockHttpServletRequest();
+ request4 = new MockHttpServletRequest();
+ request5 = new MockHttpServletRequest();
+
+ eventInJson = "{\"sourcetype\": \"mysourcetype\", \"event\": \"Hello, world!\", \"host\": \"localhost\", \"source\": \"mysource\", \"index\": \"myindex\"}";
+
+ channel1 = "CHANNEL_11111";
+ channel2 = "CHANNEL_22222";
+ channel3 = "CHANNEL_33333";
+
+ authToken1 = "AUTH_TOKEN_12223";
+ authToken2 = "AUTH_TOKEN_16664";
+ authToken3 = "AUTH_TOKEN_23667";
+ authToken4 = "AUTH_TOKEN_23249";
+
+ request1.addHeader("Authorization", authToken1);
+ request3.addHeader("Authorization", authToken2);
+ request4.addHeader("Authorization", authToken3);
+ request5.addHeader("Authorization", authToken4);
+
+ ackRequest = "{\"acks\": [1,3,4]}";
+
+ ackRequestNode = objectMapper.createObjectNode();
+
+ defaultChannel = Session.DEFAULT_CHANNEL;
+
+ try {
+ ackRequestNode = objectMapper.readTree(ackRequest);
+ } catch (IOException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ /*
+ * Tests the sendEvents() and getAcks() method of the service.
+ */
+ @Test
+ public void sendEventsAndGetAcksTest() {
+ String supposedResponse;
+
+ supposedResponse = "{\"text\":\"Success\",\"code\":0,\"ackID\":0}";
+ assertEquals("Service should return JSON object with fields 'text', 'code' and 'ackID' (ackID should be 0)",
+ supposedResponse, service.sendEvents(request1, channel3, eventInJson).toString());
+
+ supposedResponse = "{\"text\":\"Success\",\"code\":0,\"ackID\":1}";
+ assertEquals("Service should return JSON object with fields 'text', 'code' and 'ackID' (ackID should be 1)",
+ supposedResponse, service.sendEvents(request1, channel3, eventInJson).toString());
+
+ supposedResponse = "{\"text\":\"Success\",\"code\":0,\"ackID\":0}";
+ assertEquals("Service should return JSON object with fields 'text', 'code' and 'ackID' (ackID should be 0)",
+ supposedResponse, service.sendEvents(request1, channel2, eventInJson).toString());
+
+ assertEquals("Service should return JSON object with fields 'text', 'code' and 'ackID' (ackID should be 0)",
+ supposedResponse, service.sendEvents(request3, channel3, eventInJson).toString());
+
+ supposedResponse = "{\"acks\":{\"1\":true,\"3\":false,\"4\":false}}";
+ assertEquals("JSON object should be returned with ack statuses.", supposedResponse,
+ service.getAcks(request1, channel3, ackRequestNode).toString());
+ }
+
+ /*
+ * Tests sending a request with no authentication token. In this case
+ * AuthenticationTokenMissingException is expected to happen.
+ */
+ @Test
+ public void sendEventsWithoutAuthTokenTest() {
+ Assertions.assertThrows(AuthenticationTokenMissingException.class, () -> {
+ service.sendEvents(request2, eventInJson, channel1);
+ });
+ }
+
+ /*
+ * Tests sending a request with no channel provided. In this case no Ack id is
+ * returned.
+ */
+ @Test
+ public void sendEventsWithoutChannelTest() {
+ String supposedResponse = "{\"text\":\"Success\",\"code\":0}";
+ String response = service.sendEvents(request1, null, eventInJson).toString();
+ assertEquals("Service should return JSON object with fields 'text' and 'code'", supposedResponse,
+ response);
+ }
+
+ /*
+ * Tests getting the Ack statuses without providing channel in the request. In
+ * this case ChannelNotProvidedException is expected to happen.
+ */
+ @Test
+ public void getAcksWithoutChannel() {
+ Assertions.assertThrows(ChannelNotProvidedException.class, () -> {
+ service.getAcks(request1, null, ackRequestNode);
+ });
+ }
+
+ /*
+ * Tests getting the Ack statuses without providing an authentication token in
+ * the request. In this case AuthenticationTokenMissingException is expected to
+ * happen.
+ */
+ @Test
+ public void getAcksWithoutAuthTokenTest() {
+ Assertions.assertThrows(AuthenticationTokenMissingException.class, () -> {
+ service.getAcks(request2, channel1, ackRequestNode);
+ });
+ }
+
+ /*
+ * Tests trying to get Ack statuses with an authentication that is not used to
+ * send events. In this case SessionNotFoundException is expected to happen.
+ */
+ @Test
+ public void getAcksWithUnusedAuthToken() {
+ Assertions.assertThrows(SessionNotFoundException.class, () -> {
+ service.getAcks(request4, channel1, ackRequestNode);
+ });
+ }
+
+ /*
+ * Tests trying to get Ack statuses with a channel that is does not exist in the
+ * session. In this case ChannelNotFoundException is expected to happen.
+ */
+ @Test
+ public void getAcksWithUnusedChannel() {
+ Assertions.assertThrows(ChannelNotFoundException.class, () -> {
+ service.sendEvents(request5, channel1, eventInJson);
+ service.getAcks(request5, channel2, ackRequestNode);
+ });
+ }
+
+ /*
+ * Tests the EventManager's convertData() method. First we create a Json node as
+ * string, that we give as a parameter for convertData(). We also create a
+ * supposed response Json node as string. convertData() returns an ObjectNode
+ * object, which we convert to string here, so we can easily compare it to our
+ * supposed response.
+ */
+ @Test
+ public void convertDataTest() {
+ /*AckManager ackManager = new AckManager();*/
+ String allEventsInJson = "{\"sourcetype\": \"mysourcetype\", \"event\": \"Hello, world!\", \"host\": \"localhost\", \"source\": \"mysource\", \"index\": \"myindex\"}";
+ String supposedResponse = "{\"text\":\"Success\",\"code\":0,\"ackID\":2}";
+ String response = eventManager.convertData(authToken1, channel1, allEventsInJson, headerInfo, ackManager).toString();
+ assertEquals("Should get a JSON with fields text, code and ackID", supposedResponse, response);
+ }
+
+ /*
+ * Tests the EventManager's convertDataWithDefaultChannel() method which is
+ * called when a channel is not provided in a request. First we create a Json
+ * node as string, that we give as a parameter for convertData(). We also create
+ * a supposed response Json node as string. convertData() returns an ObjectNode
+ * object, which we convert to string here, so we can easily compare it to our
+ * supposed response.
+ */
+ @Test
+ public void convertDataTestWithDefaultChannel() {
+ /* AckManager ackManager = new AckManager(); */
+ String allEventsInJson = "{\"sourcetype\": \"mysourcetype\", \"event\": \"Hello, world!\", \"host\": \"localhost\", \"source\": \"mysource\", \"index\": \"myindex\"}";
+ String supposedResponse = "{\"text\":\"Success\",\"code\":0}";
+
+ assertEquals("Should get a JSON with fields text and code.", supposedResponse, eventManager
+ .convertData(authToken1, defaultChannel, allEventsInJson, headerInfo, ackManager).toString());
+
+ }
+
+ /*
+ * Tests attempting to send a request, which has no "event"-field in it's body.
+ * When this is the case, EventFieldMissingException is expected to happen.
+ */
+ @Test
+ public void noEventFieldInRequestTest() {
+ Assertions.assertThrows(EventFieldMissingException.class, () -> {
+ /*AckManager ackManager = new AckManager();*/
+ String allEventsInJson = "{\"sourcetype\": \"mysourcetype\", \"host\": \"localhost\", \"source\": \"mysource\", \"index\": \"myindex\"}";
+ eventManager.convertData(authToken1, channel1, allEventsInJson, headerInfo, ackManager);
+ });
+ }
+
+ /*
+ * Tests attempting to send a request, which has a blank "event"-field. When
+ * this is the case, EventFieldBlankException is expected to happen.
+ */
+ @Test
+ public void eventFieldBlankInRequestTest() {
+ Assertions.assertThrows(EventFieldBlankException.class, () -> {
+ /*AckManager ackManager = new AckManager();*/
+ String allEventsInJson = "{\"sourcetype\": \"mysourcetype\", \"event\": \"\", \"host\": \"localhost\", \"source\": \"mysource\", \"index\": \"myindex\"}";
+ eventManager.convertData(authToken1, channel1, allEventsInJson, headerInfo, ackManager);
+ });
+ }
+
+ /*
+ * EventManager needs to handle the time stamp, if it is provided in the
+ * request. This method tests the handling of time.
+ */
+ @Test
+ public void handleTimeTest() {
+ // Content strings are created with different kinds of "time"-fields amd they
+ // are read into a JsonNode object.
+
+ // content1: time is in epoch seconds (10 digits)
+ String content1 = "{\"event\": \"Pony 1 has left the barn\", \"sourcetype\": \"mysourcetype\", \"time\": 1277464192}";
+ // content2: "time"-field is not given
+ String content2 = "{\"event\": \"Pony 1 has left the barn\", \"sourcetype\": \"mysourcetype\"}";
+ // content3: time is given in epoch seconds and a decimal giving the epoch
+ // milliseconds
+ String content3 = "{\"event\": \"Pony 1 has left the barn\", \"sourcetype\": \"mysourcetype\", \"time\": 1433188255.253}";
+ // content4: time is given inepoch milliseconds (13 digits)
+ String content4 = "{\"event\": \"Pony 1 has left the barn\", \"sourcetype\": \"mysourcetype\", \"time\": 1433188255253}";
+ // content5: time is given as a string
+ String content5 = "{\"event\": \"Pony 1 has left the barn\", \"sourcetype\": \"mysourcetype\", \"time\": \"1433188255253\"}";
+ // content6: time is given with too small amount of digits
+ String content6 = "{\"event\": \"Pony 1 has left the barn\", \"sourcetype\": \"mysourcetype\", \"time\": 143318}";
+ // content7: time is given in epoch centiseconds
+ String content7 = "{\"event\": \"Pony 1 has left the barn\", \"sourcetype\": \"mysourcetype\", \"time\": 143318825525}";
+ // content8: time is given with too many digits
+ String content8 = "{\"event\": \"Pony 1 has left the barn\", \"sourcetype\": \"mysourcetype\", \"time\": 1433188255252321}";
+
+ JsonNode node1 = null;
+ JsonNode node2 = null;
+ JsonNode node3 = null;
+ JsonNode node4 = null;
+ JsonNode node5 = null;
+ JsonNode node6 = null;
+ JsonNode node7 = null;
+ JsonNode node8 = null;
+
+ try {
+ node1 = objectMapper.readTree(content1);
+ node2 = objectMapper.readTree(content2);
+ node3 = objectMapper.readTree(content3);
+ node4 = objectMapper.readTree(content4);
+ node5 = objectMapper.readTree(content5);
+ node6 = objectMapper.readTree(content6);
+ node7 = objectMapper.readTree(content7);
+ node8 = objectMapper.readTree(content8);
+ } catch (IOException e) {
+ throw new RuntimeException(e);
+ }
+
+ HttpEventData testData1 = new HttpEventData();
+ HttpEventData testData2 = new HttpEventData();
+ HttpEventData testData3 = new HttpEventData();
+ HttpEventData testData4 = new HttpEventData();
+ HttpEventData testData5 = new HttpEventData();
+ HttpEventData testData6 = new HttpEventData();
+ HttpEventData testData7 = new HttpEventData();
+ HttpEventData testData8 = new HttpEventData();
+
+ // Accessing a private method handleTime() in EventManager and using the created
+ // nodes and empty HttpEventData objects as parameters
+ try {
+ testData1 = Whitebox.invokeMethod(eventManager, "handleTime", testData1, node1, null);
+ testData2 = Whitebox.invokeMethod(eventManager, "handleTime", testData2, node2, null);
+ testData3 = Whitebox.invokeMethod(eventManager, "handleTime", testData3, node3, null);
+ testData4 = Whitebox.invokeMethod(eventManager, "handleTime", testData4, node4, null);
+ testData5 = Whitebox.invokeMethod(eventManager, "handleTime", testData5, node5, null);
+ testData6 = Whitebox.invokeMethod(eventManager, "handleTime", testData6, node6, null);
+ testData7 = Whitebox.invokeMethod(eventManager, "handleTime", testData7, node7, null);
+ testData8 = Whitebox.invokeMethod(eventManager, "handleTime", testData8, node8, null);
+ } catch (Exception e) {
+ LOGGER.warn("Could not invokeMethods properly: ", e);
+ }
+
+ /*
+ * Testing the getTimeSource(), isTimeParsed() and getTimeAsLong() methods from
+ * the HttpEventData objects that were returned from EventManager's handleTime()
+ * method.
+ */
+ assertEquals("Time source should be 'reported' when the time is specified in a request", "reported",
+ testData1.getTimeSource());
+ assertTrue("timeParsed should be true when the time is specified in a request", testData1.isTimeParsed());
+ assertEquals("Time should have been converted to epoch milliseconds", 1277464192000L,
+ testData1.getTimeAsLong());
+
+ assertEquals("Time source should be 'generated' when it's not specified in a request", "generated",
+ testData2.getTimeSource());
+ assertFalse("timeParsed should be false when time is not specified in a request", testData2.isTimeParsed());
+ assertEquals("Time as long should be 0 when time is not specified in a request", 0, testData2.getTimeAsLong());
+
+ assertEquals("Time source should be 'reported' when the time is specified in a request", "reported",
+ testData3.getTimeSource());
+ assertTrue("timeParsed should be true when time is specified in a request.", testData3.isTimeParsed());
+ assertEquals(
+ "Time should be converted to epoch milliseconds when it's provided in a request in epoch seconds with decimals.",
+ 1433188255253L, testData3.getTimeAsLong());
+
+ assertEquals("Time source should be 'reported' when the time is specified in a request", "reported",
+ testData4.getTimeSource());
+ assertTrue("timeParsed should be true when time is specified in a request.", testData4.isTimeParsed());
+ assertEquals("Time should be in epoch milliseconds when it is provided as epoch milliseconds in the request",
+ 1433188255253L, testData4.getTimeAsLong());
+
+ assertEquals("Time source should be 'generated' when time is given as a string in a request", "generated",
+ testData5.getTimeSource());
+ assertFalse("timeParsed should be false when time is given as a string in a request", testData5.isTimeParsed());
+ assertEquals("Time should be 0 when time is given as a string in a request", 0, testData5.getTimeAsLong());
+
+ assertEquals("Time source should be 'generated' when time is given as an integer with less than 10 digits",
+ "generated", testData6.getTimeSource());
+ assertFalse("timeParsed should be false when time is given as an integer with less than 10 digits",
+ testData6.isTimeParsed());
+ assertEquals("Time as long should be as provided in the request.", 143318, testData6.getTimeAsLong());
+
+ assertEquals("Time source should be 'reported' when the time is specified in a request with 10-13 digits",
+ "reported", testData7.getTimeSource());
+ assertTrue("timeParsed should be true when time is specified in a request with 10-13 digits",
+ testData7.isTimeParsed());
+ assertEquals("Time should be converted to epoch milliseconds when provided in a request with 10-13 digits",
+ 1433188255250L, testData7.getTimeAsLong());
+
+ assertEquals("Time source should be 'generated' when time is given as an integer with more than 13 digits",
+ "generated", testData8.getTimeSource());
+ assertFalse("timeParsed should be false when time is given as an integer with more than 13 digits",
+ testData8.isTimeParsed());
+ assertEquals("Time should be as it's provided in a request.", 1433188255252321L, testData8.getTimeAsLong());
+ }
+
+ /*
+ * Testing using EventManager's convertData() method by sending multiple events
+ * at once.
+ */
+ public void sendingMultipleEventsTest() {
+ AckManager ackManager = new AckManager();
+ String allEventsInJson = "{\"event\": \"Pony 1 has left the barn\", \"sourcetype\": \"mysourcetype\", \"time\": 1426279439}{\"event\": \"Pony 2 has left the barn\"}{\"event\": \"Pony 3 has left the barn\", \"sourcetype\": \"newsourcetype\"}{\"event\": \"Pony 4 has left the barn\"}";
+ String supposedResponse = "{\"text\":\"Success\",\"code\":0,\"ackID\":0}";
+ assertEquals("Should get a JSON with fields text, code and ackID", supposedResponse,
+ eventManager.convertData(authToken1, channel1, allEventsInJson, headerInfo, ackManager).toString());
+
+ }
+
+ /*
+ * Testing using EventManager's convertDataWithDefaultChannel() method by
+ * sending multiple events at once.
+ */
+ public void sendingMultipleEventsWithDefaultChannelTest() {
+ AckManager ackManager = new AckManager();
+ String allEventsInJson = "{\"event\": \"Pony 1 has left the barn\", \"sourcetype\": \"mysourcetype\", \"time\": 1426279439}{\"event\": \"Pony 2 has left the barn\"}{\"event\": \"Pony 3 has left the barn\", \"sourcetype\": \"newsourcetype\"}{\"event\": \"Pony 4 has left the barn\"}";
+ String supposedResponse = "{\"text\":\"Success\",\"code\":0,\"ackID\":0}";
+ assertEquals("Should get a JSON with fields text, code and ackID", supposedResponse, eventManager
+ .convertData(authToken1, defaultChannel, allEventsInJson, headerInfo, ackManager).toString());
+ }
+}
diff --git a/src/test/resources/cfe-16-test-plan.jmx b/src/test/resources/cfe-16-test-plan.jmx
new file mode 100644
index 0000000..882e83c
--- /dev/null
+++ b/src/test/resources/cfe-16-test-plan.jmx
@@ -0,0 +1,303 @@
+
+
+
+
+
+ false
+ false
+
+
+
+
+
+
+
+
+
+ host
+ wsf.cdyne.com
+ =
+ Host of Webservice
+
+
+
+
+
+ continue
+
+ false
+ -1
+
+ 8
+ 0
+ 1375525852000
+ 1375525852000
+ true
+ 30
+
+
+
+
+ true
+
+
+
+ false
+ {"sourcetype": "mysourcetype", "event": "Hello, world!"}
+
+ =
+
+
+
+ 127.0.0.1
+ 8080
+ http
+
+ /services/collector?channel=CHANNEL_11111
+ POST
+ true
+ false
+ true
+ false
+
+
+
+
+
+
+
+
+ Authorization
+ AUTH_TOKEN_11111
+
+
+ Content-Type
+ application/json; charset=utf-8
+
+
+
+
+
+
+ </GetCityForecastByZIPResult>
+
+ Verify content in response
+ Assertion.response_data
+ false
+ 16
+
+
+
+
+
+ true
+
+
+
+ false
+ {"sourcetype": "mysourcetype", "event": "Hello, world!"}
+
+ =
+
+
+
+ 127.0.0.1
+ 8080
+ http
+
+ /services/collector?channel=CHANNEL_22222
+ POST
+ true
+ false
+ true
+ false
+
+
+
+
+
+
+
+
+ Authorization
+ AUTH_TOKEN_11111
+
+
+ Content-Type
+ application/json; charset=utf-8
+
+
+
+
+
+
+ </GetCityForecastByZIPResult>
+
+ Verify content in response
+ Assertion.response_data
+ false
+ 16
+
+
+
+
+
+ true
+
+
+
+ false
+ {"sourcetype": "mysourcetype", "event": "Hello, world!"}
+
+ =
+
+
+
+ 127.0.0.1
+ 8080
+ http
+
+ /services/collector?channel=CHANNEL_11111
+ POST
+ true
+ false
+ true
+ false
+
+
+
+
+
+
+
+
+ Authorization
+ AUTH_TOKEN_11111
+
+
+ Content-Type
+ application/json; charset=utf-8
+
+
+
+
+
+
+ </GetCityForecastByZIPResult>
+
+ Verify content in response
+ Assertion.response_data
+ false
+ 16
+
+
+
+
+
+ true
+
+
+
+ false
+ {"sourcetype": "mysourcetype", "event": "Hello, world!"}
+
+ =
+
+
+
+ 127.0.0.1
+ 8080
+ http
+
+ /services/collector?channel=CHANNEL_33333
+ POST
+ true
+ false
+ true
+ false
+
+
+
+
+
+
+
+
+ Authorization
+ AUTH_TOKEN_22222
+
+
+ Content-Type
+ application/json; charset=utf-8
+
+
+
+
+
+
+ </GetCityForecastByZIPResult>
+
+ Verify content in response
+ Assertion.response_data
+ false
+ 16
+
+
+
+
+
+ true
+
+
+
+ false
+ {"sourcetype": "mysourcetype", "event": "Hello, world!"}
+
+ =
+
+
+
+ 127.0.0.1
+ 8080
+ http
+
+ /services/collector?channel=CHANNEL_11111
+ POST
+ true
+ false
+ true
+ false
+
+
+
+
+
+
+
+
+ Authorization
+ AUTH_TOKEN_22222
+
+
+ Content-Type
+ application/json; charset=utf-8
+
+
+
+
+
+
+ </GetCityForecastByZIPResult>
+
+ Verify content in response
+ Assertion.response_data
+ false
+ 16
+
+
+
+
+
+
+
+
diff --git a/statistics.ods b/statistics.ods
new file mode 100644
index 0000000..cc4aa52
Binary files /dev/null and b/statistics.ods differ