+
+
+
+
\ No newline at end of file
diff --git a/.idea/codeStyles/codeStyleConfig.xml b/.idea/codeStyles/codeStyleConfig.xml
new file mode 100644
index 00000000..79ee123c
--- /dev/null
+++ b/.idea/codeStyles/codeStyleConfig.xml
@@ -0,0 +1,5 @@
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/runConfigurations/00_ServiceRegistry.xml b/.idea/runConfigurations/00_ServiceRegistry.xml
new file mode 100644
index 00000000..eb706536
--- /dev/null
+++ b/.idea/runConfigurations/00_ServiceRegistry.xml
@@ -0,0 +1,17 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/runConfigurations/00_ServiceRegistry__TLS_.xml b/.idea/runConfigurations/00_ServiceRegistry__TLS_.xml
new file mode 100644
index 00000000..dc77cdf0
--- /dev/null
+++ b/.idea/runConfigurations/00_ServiceRegistry__TLS_.xml
@@ -0,0 +1,17 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/runConfigurations/01_Authorization.xml b/.idea/runConfigurations/01_Authorization.xml
new file mode 100644
index 00000000..1832c303
--- /dev/null
+++ b/.idea/runConfigurations/01_Authorization.xml
@@ -0,0 +1,17 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/runConfigurations/01_Authorization__TLS_.xml b/.idea/runConfigurations/01_Authorization__TLS_.xml
new file mode 100644
index 00000000..6db53e14
--- /dev/null
+++ b/.idea/runConfigurations/01_Authorization__TLS_.xml
@@ -0,0 +1,17 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/runConfigurations/01_EventHandler.xml b/.idea/runConfigurations/01_EventHandler.xml
new file mode 100644
index 00000000..dcebdcb5
--- /dev/null
+++ b/.idea/runConfigurations/01_EventHandler.xml
@@ -0,0 +1,17 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/runConfigurations/01_EventHandler__TLS_.xml b/.idea/runConfigurations/01_EventHandler__TLS_.xml
new file mode 100644
index 00000000..f1173bc0
--- /dev/null
+++ b/.idea/runConfigurations/01_EventHandler__TLS_.xml
@@ -0,0 +1,17 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/runConfigurations/01_Gatekeeper.xml b/.idea/runConfigurations/01_Gatekeeper.xml
new file mode 100644
index 00000000..428171b8
--- /dev/null
+++ b/.idea/runConfigurations/01_Gatekeeper.xml
@@ -0,0 +1,17 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/runConfigurations/01_Gatekeeper__TLS_.xml b/.idea/runConfigurations/01_Gatekeeper__TLS_.xml
new file mode 100644
index 00000000..f76b1d83
--- /dev/null
+++ b/.idea/runConfigurations/01_Gatekeeper__TLS_.xml
@@ -0,0 +1,17 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/runConfigurations/01_Gateway.xml b/.idea/runConfigurations/01_Gateway.xml
new file mode 100644
index 00000000..fa99b23d
--- /dev/null
+++ b/.idea/runConfigurations/01_Gateway.xml
@@ -0,0 +1,17 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/runConfigurations/01_Gateway__TLS_.xml b/.idea/runConfigurations/01_Gateway__TLS_.xml
new file mode 100644
index 00000000..97b9daf0
--- /dev/null
+++ b/.idea/runConfigurations/01_Gateway__TLS_.xml
@@ -0,0 +1,17 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/runConfigurations/01_Orchestrator.xml b/.idea/runConfigurations/01_Orchestrator.xml
new file mode 100644
index 00000000..7a2da36c
--- /dev/null
+++ b/.idea/runConfigurations/01_Orchestrator.xml
@@ -0,0 +1,17 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/runConfigurations/01_Orchestrator__TLS_.xml b/.idea/runConfigurations/01_Orchestrator__TLS_.xml
new file mode 100644
index 00000000..d1a54b45
--- /dev/null
+++ b/.idea/runConfigurations/01_Orchestrator__TLS_.xml
@@ -0,0 +1,17 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/runConfigurations/Core_Systems.xml b/.idea/runConfigurations/Core_Systems.xml
new file mode 100644
index 00000000..a6a34405
--- /dev/null
+++ b/.idea/runConfigurations/Core_Systems.xml
@@ -0,0 +1,11 @@
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/runConfigurations/Core_Systems__TLS_.xml b/.idea/runConfigurations/Core_Systems__TLS_.xml
new file mode 100644
index 00000000..62465bcd
--- /dev/null
+++ b/.idea/runConfigurations/Core_Systems__TLS_.xml
@@ -0,0 +1,11 @@
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.travis.yml b/.travis.yml
new file mode 100644
index 00000000..c0f28cfa
--- /dev/null
+++ b/.travis.yml
@@ -0,0 +1,2 @@
+language: java
+jdk: oraclejdk8
diff --git a/CHANGELOG.md b/CHANGELOG.md
new file mode 100644
index 00000000..5920de27
--- /dev/null
+++ b/CHANGELOG.md
@@ -0,0 +1,56 @@
+### November 12th, 2018
+* All dependencies updated to their latest versions and code base now runs on Java 11
+* Changes to support MySQL 8 server
+* Most core systems now have their debian packages, which offers fast installation on Debian based Linux systems. See [Debian Packages](https://github.com/arrowhead-f/core-java/tree/develop/documentation/Debian%20Packages) for more details.
+* Added windows batch files for starting/stopping the core systems
+* Device Registry progress
+* Added version based ArrowheadService filtering to Service Registry querying
+* Service Registry management API also supports regular expression based querying
+* Many bug fixes
+
+### October 1st, 2018
+* Input validation properly works now, used the incorrect dependencies before
+* Exception handling improved
+* Fixed some NullPointerException sources
+* Removed `broker_name` and `authentication_info` columns from `broker` database table, unique constraint also deleted
+* Added [Swagger UI](https://swagger.io/tools/swagger-ui/) to each core system, to help the discoverability of the API
+* Merged the `feature/dev_sys_reg` branch into the `develop` branch, which contains the updated System Registry core system
+
+### September 24th, 2018
+* `README.md` now has a detailed guide on how to setup an arrowhead cloud from source
+* The Gatekeeper AccessControlFilter had a critical bug fixed
+* Added a 2nd relay test certificate
+* Basic continuous integration tooling added to the project
+* Core systems now wait 10 second and retry with the service registration 3 times, when the SR is not available at start (makes core system
+deployment easier)
+
+### September 12th, 2018
+* Changed the way config files (properties file before) are processed by the core systems. Now there is a `default.conf` for each core system,
+containing default values, and these values can be overridden with key-value pairs placed in a `app.conf` file, which only has to contain the
+properties with the new values.
+
+### September 7th, 2018
+* Certificate Authority core system is ready for use now. It uses the Bouncy Castle library. There is a certificate requester client inside the
+client-java repository, which connects to this core system, and creates a usable keystore from the response.
+
+### September 5th, 2018
+* AccessControlFilters now have an abstract parent class to avoid duplicate codes
+* Added 2 different certificate signing method to the CA module, both of which still need thorough testing
+
+### August 27th, 2018
+* Fixed the registered packages on web server startup
+* ArrowheadCloud and ArrowheadSystem names now allow for the following special characters too: _ - :
+* Added custom exception to certificate validation path related errors, when sending request to other systems
+* ServiceRegistryEntry contains a String metadata field again (With the current security metadata, this is needed to be able to provide a service
+in secure and insecure mode at the same time)
+* Fixed a bug in Gatekeeper AccessControlFilter, preventing GSD/ICN process in secure mode
+* Secure and insecure mode of the full framework can now work at the same time with the same database, if the ArrowheadCloud (and OwnCloud +
+NeighborCloud) table contains both Gatekeeper versions.
+
+### August 13th, 2018
+* Our complicated custom input validation solution replaced with a clean, easy to use annotation based validation library (Hibernate validator)
+* Management API refactorings, to provide ID-field based resource query, update and delete operations
+* Common resources (services, systems, clouds, devices) have their management REST endpoints in the common module now, so each module can provide their own endpoints for their database
+* Complex entity classes now use Hibernate's @OnDelete annotation to delete child entities, when a parent entity gets deleted
+* Created new common resource: ArrowheadDevice
+* Service metadata is moved back to ArrowheadService, and the port field is moved back to ArrowheadSystem (from ServiceRegistryEntry)
diff --git a/LICENSE b/LICENSE
new file mode 100644
index 00000000..261eeb9e
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,201 @@
+ Apache License
+ Version 2.0, January 2004
+ http://www.apache.org/licenses/
+
+ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
+
+ 1. Definitions.
+
+ "License" shall mean the terms and conditions for use, reproduction,
+ and distribution as defined by Sections 1 through 9 of this document.
+
+ "Licensor" shall mean the copyright owner or entity authorized by
+ the copyright owner that is granting the License.
+
+ "Legal Entity" shall mean the union of the acting entity and all
+ other entities that control, are controlled by, or are under common
+ control with that entity. For the purposes of this definition,
+ "control" means (i) the power, direct or indirect, to cause the
+ direction or management of such entity, whether by contract or
+ otherwise, or (ii) ownership of fifty percent (50%) or more of the
+ outstanding shares, or (iii) beneficial ownership of such entity.
+
+ "You" (or "Your") shall mean an individual or Legal Entity
+ exercising permissions granted by this License.
+
+ "Source" form shall mean the preferred form for making modifications,
+ including but not limited to software source code, documentation
+ source, and configuration files.
+
+ "Object" form shall mean any form resulting from mechanical
+ transformation or translation of a Source form, including but
+ not limited to compiled object code, generated documentation,
+ and conversions to other media types.
+
+ "Work" shall mean the work of authorship, whether in Source or
+ Object form, made available under the License, as indicated by a
+ copyright notice that is included in or attached to the work
+ (an example is provided in the Appendix below).
+
+ "Derivative Works" shall mean any work, whether in Source or Object
+ form, that is based on (or derived from) the Work and for which the
+ editorial revisions, annotations, elaborations, or other modifications
+ represent, as a whole, an original work of authorship. For the purposes
+ of this License, Derivative Works shall not include works that remain
+ separable from, or merely link (or bind by name) to the interfaces of,
+ the Work and Derivative Works thereof.
+
+ "Contribution" shall mean any work of authorship, including
+ the original version of the Work and any modifications or additions
+ to that Work or Derivative Works thereof, that is intentionally
+ submitted to Licensor for inclusion in the Work by the copyright owner
+ or by an individual or Legal Entity authorized to submit on behalf of
+ the copyright owner. For the purposes of this definition, "submitted"
+ means any form of electronic, verbal, or written communication sent
+ to the Licensor or its representatives, including but not limited to
+ communication on electronic mailing lists, source code control systems,
+ and issue tracking systems that are managed by, or on behalf of, the
+ Licensor for the purpose of discussing and improving the Work, but
+ excluding communication that is conspicuously marked or otherwise
+ designated in writing by the copyright owner as "Not a Contribution."
+
+ "Contributor" shall mean Licensor and any individual or Legal Entity
+ on behalf of whom a Contribution has been received by Licensor and
+ subsequently incorporated within the Work.
+
+ 2. Grant of Copyright License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ copyright license to reproduce, prepare Derivative Works of,
+ publicly display, publicly perform, sublicense, and distribute the
+ Work and such Derivative Works in Source or Object form.
+
+ 3. Grant of Patent License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ (except as stated in this section) patent license to make, have made,
+ use, offer to sell, sell, import, and otherwise transfer the Work,
+ where such license applies only to those patent claims licensable
+ by such Contributor that are necessarily infringed by their
+ Contribution(s) alone or by combination of their Contribution(s)
+ with the Work to which such Contribution(s) was submitted. If You
+ institute patent litigation against any entity (including a
+ cross-claim or counterclaim in a lawsuit) alleging that the Work
+ or a Contribution incorporated within the Work constitutes direct
+ or contributory patent infringement, then any patent licenses
+ granted to You under this License for that Work shall terminate
+ as of the date such litigation is filed.
+
+ 4. Redistribution. You may reproduce and distribute copies of the
+ Work or Derivative Works thereof in any medium, with or without
+ modifications, and in Source or Object form, provided that You
+ meet the following conditions:
+
+ (a) You must give any other recipients of the Work or
+ Derivative Works a copy of this License; and
+
+ (b) You must cause any modified files to carry prominent notices
+ stating that You changed the files; and
+
+ (c) You must retain, in the Source form of any Derivative Works
+ that You distribute, all copyright, patent, trademark, and
+ attribution notices from the Source form of the Work,
+ excluding those notices that do not pertain to any part of
+ the Derivative Works; and
+
+ (d) If the Work includes a "NOTICE" text file as part of its
+ distribution, then any Derivative Works that You distribute must
+ include a readable copy of the attribution notices contained
+ within such NOTICE file, excluding those notices that do not
+ pertain to any part of the Derivative Works, in at least one
+ of the following places: within a NOTICE text file distributed
+ as part of the Derivative Works; within the Source form or
+ documentation, if provided along with the Derivative Works; or,
+ within a display generated by the Derivative Works, if and
+ wherever such third-party notices normally appear. The contents
+ of the NOTICE file are for informational purposes only and
+ do not modify the License. You may add Your own attribution
+ notices within Derivative Works that You distribute, alongside
+ or as an addendum to the NOTICE text from the Work, provided
+ that such additional attribution notices cannot be construed
+ as modifying the License.
+
+ You may add Your own copyright statement to Your modifications and
+ may provide additional or different license terms and conditions
+ for use, reproduction, or distribution of Your modifications, or
+ for any such Derivative Works as a whole, provided Your use,
+ reproduction, and distribution of the Work otherwise complies with
+ the conditions stated in this License.
+
+ 5. Submission of Contributions. Unless You explicitly state otherwise,
+ any Contribution intentionally submitted for inclusion in the Work
+ by You to the Licensor shall be under the terms and conditions of
+ this License, without any additional terms or conditions.
+ Notwithstanding the above, nothing herein shall supersede or modify
+ the terms of any separate license agreement you may have executed
+ with Licensor regarding such Contributions.
+
+ 6. Trademarks. This License does not grant permission to use the trade
+ names, trademarks, service marks, or product names of the Licensor,
+ except as required for reasonable and customary use in describing the
+ origin of the Work and reproducing the content of the NOTICE file.
+
+ 7. Disclaimer of Warranty. Unless required by applicable law or
+ agreed to in writing, Licensor provides the Work (and each
+ Contributor provides its Contributions) on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+ implied, including, without limitation, any warranties or conditions
+ of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
+ PARTICULAR PURPOSE. You are solely responsible for determining the
+ appropriateness of using or redistributing the Work and assume any
+ risks associated with Your exercise of permissions under this License.
+
+ 8. Limitation of Liability. In no event and under no legal theory,
+ whether in tort (including negligence), contract, or otherwise,
+ unless required by applicable law (such as deliberate and grossly
+ negligent acts) or agreed to in writing, shall any Contributor be
+ liable to You for damages, including any direct, indirect, special,
+ incidental, or consequential damages of any character arising as a
+ result of this License or out of the use or inability to use the
+ Work (including but not limited to damages for loss of goodwill,
+ work stoppage, computer failure or malfunction, or any and all
+ other commercial damages or losses), even if such Contributor
+ has been advised of the possibility of such damages.
+
+ 9. Accepting Warranty or Additional Liability. While redistributing
+ the Work or Derivative Works thereof, You may choose to offer,
+ and charge a fee for, acceptance of support, warranty, indemnity,
+ or other liability obligations and/or rights consistent with this
+ License. However, in accepting such obligations, You may act only
+ on Your own behalf and on Your sole responsibility, not on behalf
+ of any other Contributor, and only if You agree to indemnify,
+ defend, and hold each Contributor harmless for any liability
+ incurred by, or claims asserted against, such Contributor by reason
+ of your accepting any such warranty or additional liability.
+
+ END OF TERMS AND CONDITIONS
+
+ APPENDIX: How to apply the Apache License to your work.
+
+ To apply the Apache License to your work, attach the following
+ boilerplate notice, with the fields enclosed by brackets "[]"
+ replaced with your own identifying information. (Don't include
+ the brackets!) The text should be enclosed in the appropriate
+ comment syntax for the file format. We also recommend that a
+ file or class name and description of purpose be included on the
+ same "printed page" as the copyright notice for easier
+ identification within third-party archives.
+
+ Copyright [yyyy] [name of copyright owner]
+
+ 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.
diff --git a/README.md b/README.md
index 81ecdcfa..6e6e1827 100644
--- a/README.md
+++ b/README.md
@@ -10,4 +10,3 @@
### Build and run
### Windows 10 Install Guide
-
diff --git a/_config.yml b/_config.yml
new file mode 100644
index 00000000..c4192631
--- /dev/null
+++ b/_config.yml
@@ -0,0 +1 @@
+theme: jekyll-theme-cayman
\ No newline at end of file
diff --git a/authorization/config/certificates/authorization.p12 b/authorization/config/certificates/authorization.p12
new file mode 100644
index 00000000..bcb8f5a4
Binary files /dev/null and b/authorization/config/certificates/authorization.p12 differ
diff --git a/authorization/config/certificates/truststore.p12 b/authorization/config/certificates/truststore.p12
new file mode 100644
index 00000000..aa386178
Binary files /dev/null and b/authorization/config/certificates/truststore.p12 differ
diff --git a/authorization/config/default.conf b/authorization/config/default.conf
new file mode 100644
index 00000000..eb8cb01d
--- /dev/null
+++ b/authorization/config/default.conf
@@ -0,0 +1,73 @@
+#
+# This work is part of the Productive 4.0 innovation project, which receives grants from the
+# European Commissions H2020 research and innovation programme, ECSEL Joint Undertaking
+# (project no. 737459), the free state of Saxony, the German Federal Ministry of Education and
+# national funding authorities from involved countries.
+#
+
+############################################
+### APPLICATION PARAMETERS ###
+############################################
+
+# Database connection (mandatory)
+db_user=arrowhead
+db_password=arrowhead
+db_address=jdbc:mysql://127.0.0.1:3306/arrowhead
+
+# Certificate related paths and passwords (mandatory in secure mode)
+keystore=config/certificates/authorization.p12
+keystorepass=123456
+keypass=123456
+truststore=config/certificates/truststore.p12
+truststorepass=123456
+
+# Authorization web-server parameters
+address=0.0.0.0
+insecure_port=8444
+secure_port=8445
+
+# Service Registry web-server parameters (to register the Authorization services)
+sr_address=0.0.0.0
+sr_insecure_port=8442
+sr_secure_port=8443
+
+#Allow querying access to the authorization tables for application systems (true/false - only has effect in secure mode)
+enable_auth_for_cloud=false
+
+
+############################################
+### LOGGING PARAMETERS ###
+############################################
+
+# Define the root logger with appender file
+log4j.rootLogger=INFO, DB, FILE
+# Database related config
+# Define the DB appender
+log4j.appender.DB=org.apache.log4j.jdbc.JDBCAppender
+# Set Database URL
+log4j.appender.DB.URL=jdbc:mysql://127.0.0.1:3306/arrowhead?autoReconnect=true
+# Set database user name and password
+log4j.appender.DB.user=arrowhead
+log4j.appender.DB.password=arrowhead
+# Set the SQL statement to be executed.
+log4j.appender.DB.sql=INSERT INTO logs(id, date, origin, level, message) VALUES(DEFAULT,'%d{yyyy-MM-dd HH:mm:ss}','%C','%p','%m')
+# Define the layout for file appender
+log4j.appender.DB.layout=org.apache.log4j.PatternLayout
+# Disable Hibernate verbose logging
+log4j.logger.org.hibernate=fatal
+
+
+# File related config
+# Define the file appender
+log4j.appender.FILE=org.apache.log4j.FileAppender
+# Set the name of the file
+log4j.appender.FILE.File=log4j_log.txt
+# Set the immediate flush to true (default)
+log4j.appender.FILE.ImmediateFlush=true
+# Set the threshold to debug mode
+log4j.appender.FILE.Threshold=debug
+# Set the append to false, overwrite
+log4j.appender.FILE.Append=false
+# Define the layout for file appender
+log4j.appender.FILE.layout=org.apache.log4j.PatternLayout
+log4j.appender.FILE.layout.conversionPattern=%d{yyyy-MM-dd HH:mm:ss}, %C, %p, %m%n
diff --git a/authorization/pom.xml b/authorization/pom.xml
new file mode 100644
index 00000000..74de0a63
--- /dev/null
+++ b/authorization/pom.xml
@@ -0,0 +1,192 @@
+
+
+
+ 4.0.0
+
+
+ eu.arrowhead
+ core
+ ${revision}
+
+
+ arrowhead-authorization
+ jar
+
+
+ 1.59
+ 1.8
+ 1.8
+ 3.8.0
+ 2.10
+ 3.0.2
+ 2.7
+
+
+
+
+
+ eu.arrowhead
+ arrowhead-core-common
+
+
+
+ org.bouncycastle
+ bcprov-jdk15on
+ ${bouncy.castle.version}
+
+
+
+
+
+
+
+ org.apache.maven.plugins
+ maven-compiler-plugin
+ ${maven.compiler.version}
+
+
+ default-compile
+
+ true
+ true
+
+ ${maven.compiler.target}
+ ${maven.compiler.source}
+
+
+
+
+
+ ${maven.compiler.source}
+ ${maven.compiler.target}
+
+
+
+
+ org.apache.maven.plugins
+ maven-jar-plugin
+ ${maven.jar.version}
+
+
+
+
+ config/
+
+
+ true
+ eu.arrowhead.core.authorization.AuthorizationMain
+ lib/
+
+
+
+
+
+
+ org.apache.maven.plugins
+ maven-dependency-plugin
+ ${maven.dependency.version}
+
+
+ copy-dependencies
+ package
+
+ copy-dependencies
+
+
+
+ ${basedir}/target/lib/
+
+
+
+
+
+ maven-resources-plugin
+ ${maven.resources.version}
+
+
+ copy-resources
+
+ validate
+
+ copy-resources
+
+
+ ${basedir}/target/config
+
+
+ config
+
+
+
+
+
+
+
+ jdeb
+ org.vafer
+ 1.7
+
+
+ package
+
+ jdeb
+
+
+ true
+ [YYMMddHHmm].${git.commit.id.abbrev}
+ target/${project.artifactId}_${revision}.deb
+ true
+ ${basedir}/src/deb/control
+
+
+
+ file
+ ${project.build.directory}/${project.build.finalName}.jar
+
+ perm
+ /usr/share/arrowhead
+ arrowhead
+ 755
+
+
+
+
+ link
+ /usr/share/arrowhead/${project.artifactId}.jar
+ /usr/share/arrowhead/${project.build.finalName}.jar
+
+
+
+ file
+ ${project.basedir}/src/deb/arrowhead-authorization.service
+
+ perm
+ /etc/systemd/system
+ 664
+
+
+
+
+ file
+ ${project.build.directory}/lib/bcprov-jdk15on-${bouncy.castle.version}.jar
+
+ perm
+ /usr/share/arrowhead/lib
+
+
+
+
+
+
+
+
+
+
+
diff --git a/authorization/src/deb/arrowhead-authorization.service b/authorization/src/deb/arrowhead-authorization.service
new file mode 100644
index 00000000..3786572b
--- /dev/null
+++ b/authorization/src/deb/arrowhead-authorization.service
@@ -0,0 +1,15 @@
+[Unit]
+Description=arrowhead-authorization
+After=network.target mysql.target arrowhead-serviceregistry-sql.service
+Requires=arrowhead-serviceregistry-sql.service
+
+[Service]
+WorkingDirectory=/etc/arrowhead/systems/authorization
+ExecStart=/usr/bin/java -jar /usr/share/arrowhead/arrowhead-authorization.jar -d -daemon -tls
+TimeoutStopSec=5
+Type=simple
+User=arrowhead
+Group=arrowhead
+
+[Install]
+WantedBy=default.target
diff --git a/authorization/src/deb/control/control b/authorization/src/deb/control/control
new file mode 100644
index 00000000..9246b4ec
--- /dev/null
+++ b/authorization/src/deb/control/control
@@ -0,0 +1,10 @@
+Package: [[name]]
+Version: [[version]]
+Section: contrib/java
+Priority: optional
+Architecture: all
+Maintainer: Thomas Pedersen
+Homepage: http://www.arrowhead.eu
+Description: Arrowhead Authorization System
+Distribution: development
+Depends: java-runtime-headless, virtual-mysql-server, arrowhead-core-common, arrowhead-serviceregistry-sql
diff --git a/authorization/src/deb/control/postinst b/authorization/src/deb/control/postinst
new file mode 100644
index 00000000..948c59ba
--- /dev/null
+++ b/authorization/src/deb/control/postinst
@@ -0,0 +1,132 @@
+#!/bin/sh
+# postinst script for arrowhead-authorization
+#
+# see: dh_installdeb(1)
+
+set -e
+
+. /usr/share/debconf/confmodule
+
+SYSTEM_NAME="authorization"
+PKG_NAME="arrowhead-authorization"
+
+# summary of how this script can be called:
+# * `configure'
+# * `abort-upgrade'
+# * `abort-remove' `in-favour'
+#
+# * `abort-remove'
+# * `abort-deconfigure' `in-favour'
+# `removing'
+#
+# for details, see https://www.debian.org/doc/debian-policy/ or
+# the debian-policy package
+
+case "$1" in
+ configure)
+ . /usr/share/arrowhead/conf/ahconf.sh
+ SYSTEM_DIR="${AH_SYSTEMS_DIR}/${SYSTEM_NAME}"
+
+ echo "Configuring MySQL database..." >&2
+ ah_db_user
+
+ if [ ! -d "${SYSTEM_DIR}" ]; then
+ mkdir -p ${SYSTEM_DIR}
+ fi
+
+ ah_cert_signed_system ${SYSTEM_NAME}
+
+ if [ ! -f "${SYSTEM_DIR}/default.conf" ]; then
+ /bin/cat <${SYSTEM_DIR}/default.conf
+############################################
+### APPLICATION PARAMETERS ###
+############################################
+
+# Database parameters (mandatory)
+db_user=arrowhead
+db_password=${AH_PASS_DB}
+db_address=jdbc:mysql://127.0.0.1:3306/arrowhead
+
+# Certificate related paths and passwords (mandatory in secure mode)
+keystore=${SYSTEM_DIR}/${SYSTEM_NAME}.p12
+keystorepass=${AH_PASS_CERT}
+keypass=${AH_PASS_CERT}
+truststore=${AH_CONF_DIR}/truststore.p12
+truststorepass=${AH_PASS_CERT}
+
+# Authorization web-server parameters
+address=0.0.0.0
+insecure_port=8444
+secure_port=8445
+
+# Service Registry web-server parameters (to register the Authorization services)
+sr_address=0.0.0.0
+sr_insecure_port=8442
+sr_secure_port=8443
+
+#Allow querying access to the authorization tables for application systems (true/false - only has effect in secure mode)
+enable_auth_for_cloud=false
+
+############################################
+### LOGGING PARAMETERS ###
+############################################
+
+# Define the root logger with appender file
+log4j.rootLogger=INFO, DB, FILE
+
+# Database related config
+# Define the DB appender
+log4j.appender.DB=org.apache.log4j.jdbc.JDBCAppender
+# Set Database URL
+log4j.appender.DB.URL=jdbc:mysql://127.0.0.1:3306/arrowhead
+# Set database user name and password
+log4j.appender.DB.user=arrowhead
+log4j.appender.DB.password=${AH_PASS_DB}
+# Set the SQL statement to be executed.
+log4j.appender.DB.sql=INSERT INTO logs(id, date, origin, level, message) VALUES(DEFAULT,'%d{yyyy-MM-dd HH:mm:ss}','%C','%p','%m')
+# Define the layout for file appender
+log4j.appender.DB.layout=org.apache.log4j.PatternLayout
+# Disable Hibernate verbose logging
+log4j.logger.org.hibernate=fatal
+
+# File related config
+# Define the file appender
+log4j.appender.FILE=org.apache.log4j.FileAppender
+# Set the name of the file
+log4j.appender.FILE.File=/var/log/arrowhead/${SYSTEM_NAME}.log
+# Set the immediate flush to true (default)
+log4j.appender.FILE.ImmediateFlush=true
+# Set the threshold to debug mode
+log4j.appender.FILE.Threshold=debug
+# Set the append to false, overwrite
+log4j.appender.FILE.Append=false
+# Define the layout for file appender
+log4j.appender.FILE.layout=org.apache.log4j.PatternLayout
+log4j.appender.FILE.layout.conversionPattern=%d{yyyy-MM-dd HH:mm:ss}, %C, %p, %m%n
+EOF
+ chown root:arrowhead ${SYSTEM_DIR}/default.conf
+ chmod 640 ${SYSTEM_DIR}/default.conf
+ fi
+
+ #ah_log4j_conf ${SYSTEM_NAME}
+
+ echo "Restarting ${PKG_NAME}..." >&2
+ systemctl daemon-reload
+ systemctl restart ${PKG_NAME}.service
+ ;;
+
+ abort-upgrade|abort-remove|abort-deconfigure)
+ ;;
+
+ *)
+ echo "postinst called with unknown argument \`$1'" >&2
+ exit 1
+ ;;
+esac
+
+# dh_installdeb will replace this with shell code automatically
+# generated by other debhelper scripts.
+
+#DEBHELPER#
+
+exit 0
diff --git a/authorization/src/deb/control/postrm b/authorization/src/deb/control/postrm
new file mode 100644
index 00000000..d8db8b32
--- /dev/null
+++ b/authorization/src/deb/control/postrm
@@ -0,0 +1,54 @@
+#!/bin/sh
+# postrm script for arrowhead-authorization
+#
+# see: dh_installdeb(1)
+
+set -e
+
+. /usr/share/debconf/confmodule
+
+SYSTEM_NAME="authorization"
+
+# summary of how this script can be called:
+# * `remove'
+# * `purge'
+# * `upgrade'
+# * `failed-upgrade'
+# * `abort-install'
+# * `abort-install'
+# * `abort-upgrade'
+# * `disappear'
+#
+# for details, see https://www.debian.org/doc/debian-policy/ or
+# the debian-policy package
+
+
+case "$1" in
+ purge)
+ AH_CONF_DIR="/etc/arrowhead"
+ AH_SYSTEMS_DIR="${AH_CONF_DIR}/systems"
+ SYSTEM_DIR="${AH_SYSTEMS_DIR}/${SYSTEM_NAME}"
+
+ rm -f \
+ /var/log/arrowhead/${SYSTEM_NAME}.log \
+ ${SYSTEM_DIR}/default.conf \
+ ${SYSTEM_DIR}/${SYSTEM_NAME}.p12
+ rmdir ${SYSTEM_DIR} 2>/dev/null || true
+ rmdir /var/log/arrowhead 2>/dev/null || true
+ db_purge
+ ;;
+ remove|upgrade|failed-upgrade|abort-install|abort-upgrade|disappear)
+ ;;
+
+ *)
+ echo "postrm called with unknown argument \`$1'" >&2
+ exit 1
+ ;;
+esac
+
+# dh_installdeb will replace this with shell code automatically
+# generated by other debhelper scripts.
+
+#DEBHELPER#
+
+exit 0
diff --git a/authorization/src/deb/control/preinst b/authorization/src/deb/control/preinst
new file mode 100644
index 00000000..ffafdbae
--- /dev/null
+++ b/authorization/src/deb/control/preinst
@@ -0,0 +1,35 @@
+#!/bin/sh
+# preinst script for arrowhead-authorization
+#
+# see: dh_installdeb(1)
+
+set -e
+
+# summary of how this script can be called:
+# * `install'
+# * `install'
+# * `upgrade'
+# * `abort-upgrade'
+# for details, see https://www.debian.org/doc/debian-policy/ or
+# the debian-policy package
+
+
+case "$1" in
+ install|upgrade)
+ ;;
+
+ abort-upgrade)
+ ;;
+
+ *)
+ echo "preinst called with unknown argument \`$1'" >&2
+ exit 1
+ ;;
+esac
+
+# dh_installdeb will replace this with shell code automatically
+# generated by other debhelper scripts.
+
+#DEBHELPER#
+
+exit 0
diff --git a/authorization/src/deb/control/prerm b/authorization/src/deb/control/prerm
new file mode 100644
index 00000000..11ef8168
--- /dev/null
+++ b/authorization/src/deb/control/prerm
@@ -0,0 +1,44 @@
+#!/bin/sh
+# prerm script for arrowhead-authorization
+#
+# see: dh_installdeb(1)
+
+set -e
+
+PKG_NAME="arrowhead-authorization"
+
+# summary of how this script can be called:
+# * `remove'
+# * `upgrade'
+# * `failed-upgrade'
+# * `remove' `in-favour'
+# * `deconfigure' `in-favour'
+# `removing'
+#
+# for details, see https://www.debian.org/doc/debian-policy/ or
+# the debian-policy package
+
+
+case "$1" in
+ remove)
+ systemctl stop ${PKG_NAME}.service
+ ;;
+
+ upgrade|deconfigure)
+ ;;
+
+ failed-upgrade)
+ ;;
+
+ *)
+ echo "prerm called with unknown argument \`$1'" >&2
+ exit 1
+ ;;
+esac
+
+# dh_installdeb will replace this with shell code automatically
+# generated by other debhelper scripts.
+
+#DEBHELPER#
+
+exit 0
diff --git a/authorization/src/main/java/eu/arrowhead/core/authorization/AuthorizationApi.java b/authorization/src/main/java/eu/arrowhead/core/authorization/AuthorizationApi.java
new file mode 100644
index 00000000..0b44effd
--- /dev/null
+++ b/authorization/src/main/java/eu/arrowhead/core/authorization/AuthorizationApi.java
@@ -0,0 +1,514 @@
+/*
+ * This work is part of the Productive 4.0 innovation project, which receives grants from the
+ * European Commissions H2020 research and innovation programme, ECSEL Joint Undertaking
+ * (project no. 737459), the free state of Saxony, the German Federal Ministry of Education and
+ * national funding authorities from involved countries.
+ */
+
+package eu.arrowhead.core.authorization;
+
+import eu.arrowhead.common.DatabaseManager;
+import eu.arrowhead.common.database.ArrowheadCloud;
+import eu.arrowhead.common.database.ArrowheadService;
+import eu.arrowhead.common.database.ArrowheadSystem;
+import eu.arrowhead.common.database.InterCloudAuthorization;
+import eu.arrowhead.common.database.IntraCloudAuthorization;
+import eu.arrowhead.common.exception.DataNotFoundException;
+import eu.arrowhead.common.messages.InterCloudAuthEntry;
+import eu.arrowhead.common.messages.IntraCloudAuthEntry;
+import java.util.ArrayList;
+import java.util.Base64;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+import javax.validation.Valid;
+import javax.ws.rs.Consumes;
+import javax.ws.rs.DELETE;
+import javax.ws.rs.GET;
+import javax.ws.rs.POST;
+import javax.ws.rs.PUT;
+import javax.ws.rs.Path;
+import javax.ws.rs.PathParam;
+import javax.ws.rs.Produces;
+import javax.ws.rs.QueryParam;
+import javax.ws.rs.core.GenericEntity;
+import javax.ws.rs.core.MediaType;
+import javax.ws.rs.core.Response;
+import javax.ws.rs.core.Response.Status;
+import org.apache.log4j.Logger;
+
+@Path("authorization/mgmt")
+@Produces(MediaType.APPLICATION_JSON)
+@Consumes(MediaType.APPLICATION_JSON)
+public class AuthorizationApi {
+
+ private final HashMap restrictionMap = new HashMap<>();
+ private static final Logger log = Logger.getLogger(AuthorizationApi.class.getName());
+ private static final DatabaseManager dm = DatabaseManager.getInstance();
+
+ @GET
+ @Produces(MediaType.TEXT_PLAIN)
+ public String getIt() {
+ return "authorization/mgmt got it";
+ }
+
+ @GET
+ @Path("publickey")
+ @Produces(MediaType.TEXT_PLAIN)
+ public String getMyPublicKey() {
+ return Base64.getEncoder().encodeToString(AuthorizationMain.publicKey.getEncoded());
+ }
+
+ @GET
+ @Path("intracloud/{id}")
+ public IntraCloudAuthorization getIntraCloudAuthRight(@PathParam("id") long id) {
+ return dm.get(IntraCloudAuthorization.class, id).orElseThrow(
+ () -> new DataNotFoundException("Intra-Cloud Auhtorization entry not found with id: " + id));
+ }
+
+ /**
+ * Returns all the IntraCloud authorization rights from the database.
+ *
+ * @return List
+ */
+ @GET
+ @Path("intracloud")
+ public List getIntraCloudAuthRights() {
+
+ List authRights = dm.getAll(IntraCloudAuthorization.class, restrictionMap);
+ if (authRights.isEmpty()) {
+ log.info("getIntraCloudAuthRights throws DataNotFoundException.");
+ throw new DataNotFoundException("IntraCloud authorization rights were not found in the database.");
+ }
+
+ log.info("getIntraCloudAuthRights successfully returns " + authRights.size() + " entries.");
+ return authRights;
+ }
+
+ /**
+ * Returns the list of consumable Services of a System.
+ *
+ * @return List
+ */
+
+ @GET
+ @Path("intracloud/systemId/{systemId}/services")
+ public Set getSystemServices(@PathParam("systemId") long systemId,
+ @QueryParam("provider_side") boolean providerSide) {
+ ArrowheadSystem system = dm.get(ArrowheadSystem.class, systemId).orElseThrow(() -> {
+ log.info("getSystemServices throws DataNotFoundException.");
+ throw new DataNotFoundException("ArrowheadSystem not found with id:" + systemId);
+ });
+
+ if (!providerSide) {
+ restrictionMap.put("consumer", system);
+ } else {
+ restrictionMap.put("provider", system);
+ }
+ List authRightsList = dm.getAll(IntraCloudAuthorization.class, restrictionMap);
+ if (authRightsList.isEmpty()) {
+ log.info("getSystemServices throws DataNotFoundException.");
+ throw new DataNotFoundException(
+ "IntraCloud authorization rights were not found in the database for this consumer system.");
+ }
+
+ Set serviceList = new HashSet<>();
+ for (IntraCloudAuthorization authRight : authRightsList) {
+ serviceList.add(authRight.getService());
+ }
+ log.info("getSystemServices successfully returns " + serviceList.size() + " services");
+ return serviceList;
+ }
+
+ @GET
+ @Path("intracloud/systemId/{systemId}")
+ public List getSystemAuthRights(@PathParam("systemId") long systemId) {
+ ArrowheadSystem system = dm.get(ArrowheadSystem.class, systemId).orElseThrow(() -> {
+ log.info("getSystemAuthRights throws DataNotFoundException.");
+ throw new DataNotFoundException("ArrowheadSystem not found with id:" + systemId);
+ });
+
+ restrictionMap.put("consumer", system);
+ restrictionMap.put("provider", system);
+ List authRights = dm.getAllOfEither(IntraCloudAuthorization.class, restrictionMap);
+ if (authRights.isEmpty()) {
+ log.info("getSystemAuthRights throws DataNotFoundException.");
+ throw new DataNotFoundException("This System is not in the authorization database. " + system.toString());
+ }
+ log.info("getSystemAuthRights returns " + authRights.size() + " entries");
+ return authRights;
+ }
+
+ @GET
+ @Path("intracloud/servicedef/{serviceDefinition}")
+ public List getServiceIntraAuthRights(
+ @PathParam("serviceDefinition") String serviceDefinition) {
+
+ restrictionMap.put("serviceDefinition", serviceDefinition);
+ ArrowheadService service = dm.get(ArrowheadService.class, restrictionMap);
+ if (service == null) {
+ log.info("getServiceIntraAuthRights throws DataNotFoundException.");
+ throw new DataNotFoundException(
+ "The service " + serviceDefinition + " is not in the authorization database");
+ }
+
+ restrictionMap.clear();
+ restrictionMap.put("service", service);
+ List authRights = dm.getAll(IntraCloudAuthorization.class, restrictionMap);
+ if (authRights.isEmpty()) {
+ log.info("getServiceIntraAuthRights throws DataNotFoundException.");
+ throw new DataNotFoundException("This Service is not in the authorization database. " + service.toString());
+ }
+ log.info("getServiceIntraAuthRights returns " + authRights.size() + " entries");
+ return authRights;
+ }
+
+ /**
+ * Creates relations between local Systems, defining the consumable services
+ * between Systems. (Not bidirectional.) OneToMany relation between consumer and
+ * providers, OneToMany relation between consumer and services.
+ *
+ * @return JAX-RS Response with status code 201 and ArrowheadSystem entity (the
+ * consumer system)
+ */
+ @POST
+ @Path("intracloud")
+ public Response addSystemToAuthorized(@Valid IntraCloudAuthEntry entry) {
+ return addSystemToAuthorizedGeneric(entry);
+ }
+
+ public Response addSystemToAuthorizedGeneric(IntraCloudAuthEntry entry) {
+ restrictionMap.put("systemName", entry.getConsumer().getSystemName());
+ restrictionMap.put("address", entry.getConsumer().getAddress());
+ restrictionMap.put("port", entry.getConsumer().getPort());
+ ArrowheadSystem consumer = dm.get(ArrowheadSystem.class, restrictionMap);
+ if (consumer == null) {
+ log.info("Consumer System " + entry.getConsumer().getSystemName()
+ + " was not in the database, saving it now.");
+ consumer = dm.save(entry.getConsumer());
+ }
+
+ ArrowheadSystem retrievedSystem;
+ ArrowheadService retrievedService;
+ List savedAuthRights = new ArrayList<>();
+ for (ArrowheadSystem providerSystem : entry.getProviderList()) {
+ restrictionMap.clear();
+ restrictionMap.put("systemName", providerSystem.getSystemName());
+ restrictionMap.put("address", providerSystem.getAddress());
+ restrictionMap.put("port", providerSystem.getPort());
+ retrievedSystem = dm.get(ArrowheadSystem.class, restrictionMap);
+ if (retrievedSystem == null) {
+ log.info("Provider System " + providerSystem.getSystemName()
+ + " was not in the database, saving it now.");
+ retrievedSystem = dm.save(providerSystem);
+ }
+ for (ArrowheadService service : entry.getServiceList()) {
+ restrictionMap.clear();
+ restrictionMap.put("serviceDefinition", service.getServiceDefinition());
+ retrievedService = dm.get(ArrowheadService.class, restrictionMap);
+ if (retrievedService == null) {
+ log.info("Service " + service.toString() + " was not in the database, saving it now.");
+ retrievedService = dm.save(service);
+ }
+ restrictionMap.clear();
+ restrictionMap.put("consumer", consumer);
+ restrictionMap.put("provider", retrievedSystem);
+ restrictionMap.put("service", retrievedService);
+ IntraCloudAuthorization authRight = dm.get(IntraCloudAuthorization.class, restrictionMap);
+ if (authRight == null) {
+ authRight = dm.save(new IntraCloudAuthorization(consumer, retrievedSystem, retrievedService));
+ savedAuthRights.add(authRight);
+ }
+ }
+ }
+
+ log.info("addSystemToAuthorized: " + savedAuthRights.size() + " authorization rights created.");
+ GenericEntity> entity = new GenericEntity>(
+ savedAuthRights) {
+ };
+ return Response.status(Status.CREATED).entity(entity).build();
+ }
+
+ @PUT
+ @Path("intracloud")
+ public Response updateIntraEntry(@Valid IntraCloudAuthorization updatedEntry) {
+ IntraCloudAuthorization entry = dm.get(IntraCloudAuthorization.class, updatedEntry.getId())
+ .orElseThrow(() -> new DataNotFoundException(
+ "IntraCloudAuthorization entry not found with id: " + updatedEntry.getId()));
+ entry.updateEntryWith(updatedEntry);
+ entry = dm.merge(entry);
+ log.info("updateIntraEntry successfully returns.");
+ return Response.ok().entity(entry).build();
+ }
+
+ /**
+ * Deletes the IntraCloudAuthorization entry with the id specified by the path
+ * parameter.
+ */
+ @DELETE
+ @Path("intracloud/{id}")
+ public Response deleteIntraEntry(@PathParam("id") long id) {
+ return dm.get(IntraCloudAuthorization.class, id).map(entry -> {
+ dm.delete(entry);
+ log.info("deleteIntraEntry successfully returns.");
+ return Response.ok().build();
+ }).orElseThrow(() -> {
+ log.info("deleteIntraEntry had no effect.");
+ throw new DataNotFoundException("Intra-Cloud Auhtorization entry not found with id: " + id);
+ });
+ }
+
+ /**
+ * Deletes all the authorization right relations where the given System is the
+ * consumer/provider (decided by query parameter).
+ *
+ * @return JAX-RS Response with status code 200 (if delete is succesxfull) or
+ * 204 (if nothing happens).
+ */
+ @DELETE
+ @Path("intracloud/systemId/{systemId}")
+ public Response deleteSystemRelations(@PathParam("systemId") long systemId,
+ @QueryParam("provider_side") boolean providerSide) {
+ ArrowheadSystem system = dm.get(ArrowheadSystem.class, systemId).orElseThrow(() -> {
+ log.info("deleteSystemRelations throws DNF.");
+ throw new DataNotFoundException("ArrowheadSystem not found with id: " + systemId);
+ });
+
+ if (!providerSide) {
+ restrictionMap.put("consumer", system);
+ } else {
+ restrictionMap.put("provider", system);
+ }
+ List authRightsList = dm.getAll(IntraCloudAuthorization.class, restrictionMap);
+ if (!authRightsList.isEmpty()) {
+ for (IntraCloudAuthorization authRight : authRightsList) {
+ dm.delete(authRight);
+ }
+
+ log.info("deleteSystemRelations successfully returns.");
+ return Response.ok().build();
+ }
+
+ log.info("deleteSystemRelations had no effect.");
+ return Response.noContent().build();
+ }
+
+ @GET
+ @Path("intercloud/{id}")
+ public InterCloudAuthorization getInterCloudAuthRight(@PathParam("id") long id) {
+ return dm.get(InterCloudAuthorization.class, id).orElseThrow(
+ () -> new DataNotFoundException("Inter-Cloud Auhtorization entry not found with id: " + id));
+ }
+
+ /**
+ * Returns all the InterCloud authorization rights from the database.
+ *
+ * @return List
+ */
+ @GET
+ @Path("intercloud")
+ public List getInterCloudAuthRights() {
+
+ List authRights = dm.getAll(InterCloudAuthorization.class, restrictionMap);
+ if (authRights.isEmpty()) {
+ log.info("getInterCloudAuthRights throws DataNotFoundException.");
+ throw new DataNotFoundException("InterCloud authorization rights were not found in the database.");
+ }
+
+ log.info("getInterCloudAuthRights successfully returns " + authRights.size() + " entries.");
+ return authRights;
+ }
+
+ /**
+ * Returns the list of consumable Services of a Cloud.
+ *
+ * @return List
+ */
+
+ @GET
+ @Path("intercloud/operator/{operator}/cloudname/{cloudName}/services")
+ public Set getCloudServices(@PathParam("operator") String operator,
+ @PathParam("cloudName") String cloudName) {
+
+ restrictionMap.put("operator", operator);
+ restrictionMap.put("cloudName", cloudName);
+ ArrowheadCloud cloud = dm.get(ArrowheadCloud.class, restrictionMap);
+ if (cloud == null) {
+ log.info("getCloudServices throws DataNotFoundException.");
+ throw new DataNotFoundException(
+ "Consumer Cloud (" + operator + ":" + cloudName + ") is not in the authorization database");
+ }
+
+ restrictionMap.clear();
+ restrictionMap.put("cloud", cloud);
+ List authRightsList = dm.getAll(InterCloudAuthorization.class, restrictionMap);
+ Set serviceList = new HashSet<>();
+ for (InterCloudAuthorization authRight : authRightsList) {
+ serviceList.add(authRight.getService());
+ }
+
+ log.info("getCloudServices successfully returns " + serviceList.size() + " services.");
+ return serviceList;
+ }
+
+ @GET
+ @Path("intercloud/operator/{operator}/cloudname/{cloudName}")
+ public List getCloudAuthRights(@PathParam("operator") String operator,
+ @PathParam("cloudName") String cloudName) {
+
+ restrictionMap.put("operator", operator);
+ restrictionMap.put("cloudName", cloudName);
+ ArrowheadCloud cloud = dm.get(ArrowheadCloud.class, restrictionMap);
+ if (cloud == null) {
+ log.info("getCloudServices throws DataNotFoundException.");
+ throw new DataNotFoundException(
+ "Consumer Cloud (" + operator + ":" + cloudName + ") is not in the authorization database");
+ }
+
+ restrictionMap.clear();
+ restrictionMap.put("cloud", cloud);
+ List authRightsList = dm.getAll(InterCloudAuthorization.class, restrictionMap);
+ if (authRightsList.isEmpty()) {
+ log.info("getCloudAuthRights throws DataNotFoundException.");
+ throw new DataNotFoundException("This Cloud is not in the authorization database. " + cloud.toString());
+ }
+ log.info("getCloudAuthRights successfully returns " + authRightsList.size() + " auth entries.");
+ return authRightsList;
+ }
+
+ @GET
+ @Path("intercloud/servicedef/{serviceDefinition}")
+ public List getServiceInterAuthRights(
+ @PathParam("serviceDefinition") String serviceDefinition) {
+
+ restrictionMap.put("serviceDefinition", serviceDefinition);
+ ArrowheadService service = dm.get(ArrowheadService.class, restrictionMap);
+ if (service == null) {
+ log.info("getServiceInterAuthRights throws DataNotFoundException.");
+ throw new DataNotFoundException(
+ "Consumer Cloud (" + serviceDefinition + ") is not in the authorization database");
+ }
+
+ restrictionMap.clear();
+ restrictionMap.put("service", service);
+ List authRights = dm.getAll(InterCloudAuthorization.class, restrictionMap);
+ if (authRights.isEmpty()) {
+ log.info("getServiceInterAuthRights throws DataNotFoundException.");
+ throw new DataNotFoundException("This Service is not in the authorization database. " + service.toString());
+ }
+ log.info("getServiceInterAuthRights returns");
+ return authRights;
+ }
+
+ /**
+ * Adds a new Cloud and its consumable Services to the database.
+ *
+ * @return JAX-RS Response with status code 201 and ArrowheadCloud entity
+ */
+ @POST
+ @Path("intercloud")
+ public Response addCloudToAuthorized(@Valid InterCloudAuthEntry entry) {
+
+ restrictionMap.put("operator", entry.getCloud().getOperator());
+ restrictionMap.put("cloudName", entry.getCloud().getCloudName());
+ ArrowheadCloud cloud = dm.get(ArrowheadCloud.class, restrictionMap);
+ if (cloud == null) {
+ log.info("Consumer Cloud was not in the database, saving it now." + entry.getCloud().toString());
+ cloud = dm.save(entry.getCloud());
+ }
+
+ ArrowheadService retrievedService;
+ List savedAuthRights = new ArrayList<>();
+ for (ArrowheadService service : entry.getServiceList()) {
+ restrictionMap.clear();
+ restrictionMap.put("serviceDefinition", service.getServiceDefinition());
+ retrievedService = dm.get(ArrowheadService.class, restrictionMap);
+ if (retrievedService == null) {
+ log.info("Service was not in the database, saving it now." + service.toString());
+ retrievedService = dm.save(service);
+ }
+ restrictionMap.clear();
+ restrictionMap.put("cloud", cloud);
+ restrictionMap.put("service", retrievedService);
+ InterCloudAuthorization authRight = dm.get(InterCloudAuthorization.class, restrictionMap);
+ if (authRight == null) {
+ authRight = dm.save(new InterCloudAuthorization(cloud, retrievedService));
+ savedAuthRights.add(authRight);
+ }
+ }
+
+ log.info("addCloudToAuthorized: " + savedAuthRights.size() + " authorization rights created.");
+ GenericEntity> entity = new GenericEntity>(
+ savedAuthRights) {
+ };
+ return Response.status(Status.CREATED).entity(entity).build();
+ }
+
+ @PUT
+ @Path("intercloud")
+ public Response updateInterEntry(@Valid InterCloudAuthorization updatedEntry) {
+ InterCloudAuthorization entry = dm.get(InterCloudAuthorization.class, updatedEntry.getId())
+ .orElseThrow(() -> new DataNotFoundException(
+ "InterCloudAuthorization entry not found with id: " + updatedEntry.getId()));
+ entry.updateEntryWith(updatedEntry);
+ entry = dm.merge(entry);
+ log.info("InterCloudAuthorization successfully returns.");
+ return Response.ok().entity(entry).build();
+ }
+
+ /**
+ * Deletes the InterCloudAuthorization entry with the id specified by the path
+ * parameter. Returns 200 if the delete is successful, 204 (no content) if the
+ * entry was not in the database to begin with.
+ */
+ @DELETE
+ @Path("intercloud/{id}")
+ public Response deleteInterEntry(@PathParam("id") long id) {
+ return dm.get(InterCloudAuthorization.class, id).map(entry -> {
+ dm.delete(entry);
+ log.info("deleteInterEntry successfully returns.");
+ return Response.ok().build();
+ }).orElseThrow(() -> {
+ log.info("deleteInterEntry had no effect.");
+ throw new DataNotFoundException("Inter-Cloud Auhtorization entry not found with id: " + id);
+ });
+ }
+
+ /**
+ * Deletes the authorization rights of the Cloud.
+ *
+ * @return JAX-RS Response with status code 200 (if delete is successful) or 204
+ * (if nothing happens).
+ */
+ @DELETE
+ @Path("intercloud/operator/{operator}/cloudname/{cloudName}")
+ public Response deleteCloudRelations(@PathParam("operator") String operator,
+ @PathParam("cloudName") String cloudName) {
+ log.info("Entered the deleteCloudRelations method.");
+
+ restrictionMap.put("operator", operator);
+ restrictionMap.put("cloudName", cloudName);
+ ArrowheadCloud cloud = dm.get(ArrowheadCloud.class, restrictionMap);
+ if (cloud == null) {
+ log.info("deleteCloudRelations had no effect.");
+ return Response.noContent().build();
+ }
+
+ restrictionMap.clear();
+ restrictionMap.put("cloud", cloud);
+ List authRightsList = dm.getAll(InterCloudAuthorization.class, restrictionMap);
+ if (!authRightsList.isEmpty()) {
+ for (InterCloudAuthorization authRight : authRightsList) {
+ dm.delete(authRight);
+ }
+
+ log.info("deleteCloudRelations successfully returns.");
+ return Response.ok().build();
+ }
+
+ log.info("deleteCloudRelations had no effect.");
+ return Response.noContent().build();
+ }
+
+}
diff --git a/authorization/src/main/java/eu/arrowhead/core/authorization/AuthorizationMain.java b/authorization/src/main/java/eu/arrowhead/core/authorization/AuthorizationMain.java
new file mode 100644
index 00000000..5c6f3c2e
--- /dev/null
+++ b/authorization/src/main/java/eu/arrowhead/core/authorization/AuthorizationMain.java
@@ -0,0 +1,46 @@
+/*
+ * This work is part of the Productive 4.0 innovation project, which receives grants from the
+ * European Commissions H2020 research and innovation programme, ECSEL Joint Undertaking
+ * (project no. 737459), the free state of Saxony, the German Federal Ministry of Education and
+ * national funding authorities from involved countries.
+ */
+
+package eu.arrowhead.core.authorization;
+
+import java.security.KeyStore;
+import java.security.PrivateKey;
+import java.security.PublicKey;
+
+import eu.arrowhead.common.ArrowheadMain;
+import eu.arrowhead.common.misc.CoreSystem;
+import eu.arrowhead.common.misc.SecurityUtils;
+
+public class AuthorizationMain extends ArrowheadMain {
+
+ public static boolean enableAuthForCloud;
+
+ static PrivateKey privateKey;
+ static PublicKey publicKey;
+
+ private AuthorizationMain(String[] args) {
+ KeyStore keyStore = SecurityUtils.loadKeyStore(props.getProperty("keystore"),
+ props.getProperty("keystorepass"));
+ privateKey = SecurityUtils.getPrivateKey(keyStore, props.getProperty("keystorepass"));
+ publicKey = SecurityUtils.getFirstCertFromKeyStore(keyStore).getPublicKey();
+ enableAuthForCloud = props.getBooleanProperty("enable_auth_for_cloud", false);
+
+ String[] packages = { "eu.arrowhead.common", "eu.arrowhead.core.authorization" };
+ init(CoreSystem.AUTHORIZATION, args, null, packages);
+ for (String s : args) {
+ if (s.equals("-opcua")) {
+ startUaServer("authorization");
+ new AuthorizationOpcUa(getArrowheadOpcUaServer());
+ }
+ }
+ listenForInput();
+ }
+
+ public static void main(String[] args) {
+ new AuthorizationMain(args);
+ }
+}
diff --git a/authorization/src/main/java/eu/arrowhead/core/authorization/AuthorizationOpcUa.java b/authorization/src/main/java/eu/arrowhead/core/authorization/AuthorizationOpcUa.java
new file mode 100644
index 00000000..d0061fc6
--- /dev/null
+++ b/authorization/src/main/java/eu/arrowhead/core/authorization/AuthorizationOpcUa.java
@@ -0,0 +1,22 @@
+package eu.arrowhead.core.authorization;
+
+import org.apache.commons.lang3.ArrayUtils;
+import org.eclipse.milo.opcua.sdk.server.nodes.UaFolderNode;
+import org.eclipse.milo.opcua.sdk.server.nodes.UaMethodNode;
+import org.eclipse.milo.opcua.stack.core.types.builtin.unsigned.UShort;
+
+import eu.arrowhead.common.opcua.ArrowheadOpcUaServer;
+import eu.arrowhead.core.authorization.opcua.AddSystemToAuthorized;
+
+public class AuthorizationOpcUa {
+ private int namespaceIndex;
+
+ public AuthorizationOpcUa(ArrowheadOpcUaServer server) {
+ namespaceIndex = ArrayUtils.indexOf(server.getNodeContext().getNamespaceTable().toArray(),
+ "urn:arrowhead:namespace");
+ UaFolderNode authorizationFolder = server.addFolder(UShort.valueOf(namespaceIndex), "Authorization");
+ UaMethodNode addSystemToAuthorized = server.addMethodNode(UShort.valueOf(namespaceIndex), authorizationFolder,
+ "addSystemToAuthorized");
+ server.addMethodNodeInNamespace(addSystemToAuthorized, authorizationFolder, new AddSystemToAuthorized(addSystemToAuthorized));
+ }
+}
diff --git a/authorization/src/main/java/eu/arrowhead/core/authorization/AuthorizationResource.java b/authorization/src/main/java/eu/arrowhead/core/authorization/AuthorizationResource.java
new file mode 100644
index 00000000..b8775b5d
--- /dev/null
+++ b/authorization/src/main/java/eu/arrowhead/core/authorization/AuthorizationResource.java
@@ -0,0 +1,186 @@
+/*
+ * This work is part of the Productive 4.0 innovation project, which receives grants from the
+ * European Commissions H2020 research and innovation programme, ECSEL Joint Undertaking
+ * (project no. 737459), the free state of Saxony, the German Federal Ministry of Education and
+ * national funding authorities from involved countries.
+ */
+
+package eu.arrowhead.core.authorization;
+
+import eu.arrowhead.common.DatabaseManager;
+import eu.arrowhead.common.database.ArrowheadCloud;
+import eu.arrowhead.common.database.ArrowheadService;
+import eu.arrowhead.common.database.ArrowheadSystem;
+import eu.arrowhead.common.database.InterCloudAuthorization;
+import eu.arrowhead.common.database.IntraCloudAuthorization;
+import eu.arrowhead.common.exception.DataNotFoundException;
+import eu.arrowhead.common.messages.ArrowheadToken;
+import eu.arrowhead.common.messages.InterCloudAuthRequest;
+import eu.arrowhead.common.messages.InterCloudAuthResponse;
+import eu.arrowhead.common.messages.IntraCloudAuthRequest;
+import eu.arrowhead.common.messages.IntraCloudAuthResponse;
+import eu.arrowhead.common.messages.TokenData;
+import eu.arrowhead.common.messages.TokenGenerationRequest;
+import eu.arrowhead.common.messages.TokenGenerationResponse;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import javax.validation.Valid;
+import javax.ws.rs.Consumes;
+import javax.ws.rs.GET;
+import javax.ws.rs.PUT;
+import javax.ws.rs.Path;
+import javax.ws.rs.Produces;
+import javax.ws.rs.core.MediaType;
+import javax.ws.rs.core.Response;
+import javax.ws.rs.core.Response.Status;
+import org.apache.log4j.Logger;
+
+/**
+ * This is the REST resource for the Authorization Core System.
+ */
+@Path("authorization")
+@Consumes(MediaType.APPLICATION_JSON)
+@Produces(MediaType.APPLICATION_JSON)
+public class AuthorizationResource {
+
+ private final HashMap restrictionMap = new HashMap<>();
+ private static final DatabaseManager dm = DatabaseManager.getInstance();
+ private static final Logger log = Logger.getLogger(AuthorizationResource.class.getName());
+
+ @GET
+ @Produces(MediaType.TEXT_PLAIN)
+ public String getIt() {
+ return "This is the Authorization Resource.";
+ }
+
+ /**
+ * Checks whether the consumer System can use a Service from a list of provider Systems.
+ *
+ * @return IntraCloudAuthResponse
+ *
+ * @throws DataNotFoundException, BadPayloadException
+ */
+ @PUT
+ @Path("intracloud")
+ public Response isSystemAuthorized(@Valid IntraCloudAuthRequest request) {
+ restrictionMap.put("systemName", request.getConsumer().getSystemName());
+ restrictionMap.put("address", request.getConsumer().getAddress());
+ restrictionMap.put("port", request.getConsumer().getPort());
+ ArrowheadSystem consumer = dm.get(ArrowheadSystem.class, restrictionMap);
+ if (consumer == null) {
+ log.error("Consumer is not in the database. isSystemAuthorized DataNotFoundException");
+ throw new DataNotFoundException("Consumer System is not in the authorization database. " + request.getConsumer().getSystemName(),
+ Status.NOT_FOUND.getStatusCode());
+ }
+
+ IntraCloudAuthResponse response = new IntraCloudAuthResponse();
+ HashMap authorizationState = new HashMap<>();
+ restrictionMap.clear();
+ restrictionMap.put("serviceDefinition", request.getService().getServiceDefinition());
+ ArrowheadService service = dm.get(ArrowheadService.class, restrictionMap);
+ if (service == null) {
+ log.info("Service " + request.getService().toString() + " is not in the database. Returning NOT AUTHORIZED state for the consumer.");
+ for (ArrowheadSystem provider : request.getProviders()) {
+ authorizationState.put(provider, false);
+ }
+ response.setAuthorizationMap(authorizationState);
+ return Response.status(Status.OK).entity(response).build();
+ }
+
+ IntraCloudAuthorization authRight;
+ int authorizedCount = 0;
+ for (ArrowheadSystem provider : request.getProviders()) {
+ restrictionMap.clear();
+ restrictionMap.put("systemName", provider.getSystemName());
+ restrictionMap.put("address", provider.getAddress());
+ restrictionMap.put("port", provider.getPort());
+ ArrowheadSystem retrievedSystem = dm.get(ArrowheadSystem.class, restrictionMap);
+
+ restrictionMap.clear();
+ restrictionMap.put("consumer", consumer);
+ restrictionMap.put("provider", retrievedSystem);
+ restrictionMap.put("service", service);
+ authRight = dm.get(IntraCloudAuthorization.class, restrictionMap);
+
+ if (authRight == null) {
+ authorizationState.put(provider, false);
+ } else {
+ authorizationState.put(provider, true);
+ authorizedCount++;
+ }
+ }
+
+ log.info(
+ "IntraCloud auth check for consumer " + request.getConsumer().getSystemName() + " returns with " + authorizedCount + " possible provider");
+ response.setAuthorizationMap(authorizationState);
+ return Response.status(Status.OK).entity(response).build();
+ }
+
+ /**
+ * Checks whether an external Cloud can use a local Service.
+ *
+ * @return boolean
+ *
+ * @throws DataNotFoundException, BadPayloadException
+ */
+ @PUT
+ @Path("intercloud")
+ public Response isCloudAuthorized(@Valid InterCloudAuthRequest request) {
+ restrictionMap.put("operator", request.getCloud().getOperator());
+ restrictionMap.put("cloudName", request.getCloud().getCloudName());
+ ArrowheadCloud cloud = dm.get(ArrowheadCloud.class, restrictionMap);
+ if (cloud == null) {
+ log.error("Requester cloud is not in the database. isCloudAuthorized DataNotFoundException");
+ throw new DataNotFoundException("Consumer Cloud is not in the authorization database. " + request.getCloud().toString(),
+ Status.NOT_FOUND.getStatusCode());
+ }
+
+ restrictionMap.clear();
+ restrictionMap.put("serviceDefinition", request.getService().getServiceDefinition());
+ ArrowheadService service = dm.get(ArrowheadService.class, restrictionMap);
+ if (service == null) {
+ log.info("Service " + request.getService().toString() + " is not in the database. Returning NOT AUTHORIZED state for the consumer.");
+ return Response.status(Status.OK).entity(new InterCloudAuthResponse(false)).build();
+ }
+
+ InterCloudAuthorization authRight;
+ restrictionMap.clear();
+ restrictionMap.put("cloud", cloud);
+ restrictionMap.put("service", service);
+ authRight = dm.get(InterCloudAuthorization.class, restrictionMap);
+
+ boolean isAuthorized = false;
+ if (authRight != null) {
+ isAuthorized = true;
+ }
+
+ log.info("Consumer Cloud is authorized: " + isAuthorized);
+ return Response.status(Status.OK).entity(new InterCloudAuthResponse(isAuthorized)).build();
+ }
+
+ /**
+ * Generates ArrowheadTokens for each consumer/service/provider trio
+ *
+ * @return TokenGenerationResponse
+ */
+ @PUT
+ @Path("token")
+ public Response tokenGeneration(@Valid TokenGenerationRequest request) {
+ // Get the tokens from the service class (can throw run time exceptions)
+ List tokens = TokenGenerationService.generateTokens(request);
+ List tokenDataList = new ArrayList<>();
+
+ // Only add the successfully created tokens to the response, with the matching provider System
+ for (int i = 0; i < tokens.size(); i++) {
+ if (tokens.get(i) != null) {
+ TokenData tokenData = new TokenData(request.getProviders().get(i), request.getService(), tokens.get(i).getToken(),
+ tokens.get(i).getSignature());
+ tokenDataList.add(tokenData);
+ }
+ }
+
+ log.info("Token generation returns with " + tokenDataList.size() + " arrowhead tokens.");
+ return Response.status(Status.OK).entity(new TokenGenerationResponse(tokenDataList)).build();
+ }
+}
diff --git a/authorization/src/main/java/eu/arrowhead/core/authorization/TokenGenerationService.java b/authorization/src/main/java/eu/arrowhead/core/authorization/TokenGenerationService.java
new file mode 100644
index 00000000..12419900
--- /dev/null
+++ b/authorization/src/main/java/eu/arrowhead/core/authorization/TokenGenerationService.java
@@ -0,0 +1,185 @@
+/*
+ * This work is part of the Productive 4.0 innovation project, which receives grants from the
+ * European Commissions H2020 research and innovation programme, ECSEL Joint Undertaking
+ * (project no. 737459), the free state of Saxony, the German Federal Ministry of Education and
+ * national funding authorities from involved countries.
+ */
+
+package eu.arrowhead.core.authorization;
+
+import eu.arrowhead.common.Utility;
+import eu.arrowhead.common.database.ArrowheadCloud;
+import eu.arrowhead.common.database.ArrowheadSystem;
+import eu.arrowhead.common.exception.ArrowheadException;
+import eu.arrowhead.common.exception.AuthException;
+import eu.arrowhead.common.messages.ArrowheadToken;
+import eu.arrowhead.common.messages.RawTokenInfo;
+import eu.arrowhead.common.messages.TokenGenerationRequest;
+import eu.arrowhead.common.misc.SecurityUtils;
+import java.nio.charset.StandardCharsets;
+import java.security.InvalidKeyException;
+import java.security.NoSuchAlgorithmException;
+import java.security.NoSuchProviderException;
+import java.security.PublicKey;
+import java.security.Security;
+import java.security.Signature;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Base64;
+import java.util.List;
+import java.util.ServiceConfigurationError;
+import javax.crypto.Cipher;
+import javax.crypto.NoSuchPaddingException;
+import javax.ws.rs.core.Response.Status;
+import org.apache.log4j.Logger;
+import org.bouncycastle.jce.provider.BouncyCastleProvider;
+
+class TokenGenerationService {
+
+ private static final Logger log = Logger.getLogger(TokenGenerationService.class.getName());
+
+ static List generateTokens(TokenGenerationRequest request) {
+ // First get the public key for each provider
+ List publicKeys = getProviderPublicKeys(request.getProviders());
+
+ // Cryptographic object initializations
+ Security.addProvider(new BouncyCastleProvider());
+ Cipher cipher;
+ try {
+ cipher = Cipher.getInstance("RSA/NONE/PKCS1Padding", "BC");
+ } catch (NoSuchAlgorithmException | NoSuchPaddingException | NoSuchProviderException e) {
+ log.fatal("Cipher.getInstance(String) throws exception, code needs to be changed!");
+ throw new AssertionError("Cipher.getInstance(String) throws exception, code needs to be changed!", e);
+ }
+ Signature signature;
+ try {
+ signature = Signature.getInstance("SHA256withRSA", "BC");
+ signature.initSign(AuthorizationMain.privateKey);
+ } catch (NoSuchAlgorithmException | NoSuchProviderException e) {
+ log.fatal("Signature.getInstance(String) throws exception, code needs to be changed!");
+ throw new AssertionError("Signature.getInstance(String) throws exception, code needs to be changed!", e);
+ } catch (InvalidKeyException e) {
+ log.fatal("The private key of the Authorization module is invalid, keystore needs to be changed!");
+ throw new ServiceConfigurationError("The private key of the Authorization module is invalid, keystore needs to be changed!", e);
+ }
+
+ // Create the ArrowheadToken for each provider
+ RawTokenInfo rawTokenInfo = new RawTokenInfo();
+ List tokens = new ArrayList<>();
+ for (PublicKey key : publicKeys) {
+ // Can not generate token without the provider public key
+ if (key == null) {
+ tokens.add(null);
+ continue;
+ }
+
+ // Set consumer info string
+ String c = request.getConsumer().getSystemName();
+ if (request.getConsumerCloud() != null) {
+ c = c.concat(".").concat(request.getConsumerCloud().getCloudName()).concat(".").concat(request.getConsumerCloud().getOperator());
+ } else {
+ boolean secureMode = Boolean.valueOf(System.getProperty("is_secure", "false"));
+ ArrowheadCloud ownCloud = Utility.getOwnCloud(secureMode);
+ c = c.concat(".").concat(ownCloud.getCloudName()).concat(".").concat(ownCloud.getOperator());
+ }
+ rawTokenInfo.setC(c);
+
+ String s = request.getService().getServiceDefinition();
+ // Set service info string
+ List interfaces = new ArrayList<>(request.getService().getInterfaces());
+ if (!interfaces.isEmpty()) {
+ s = interfaces.get(0) + "." + s;
+ }
+ rawTokenInfo.setS(s);
+
+ // Set the token validity duration
+ if (request.getDuration() != 0) {
+ long endTime = System.currentTimeMillis() + request.getDuration();
+ rawTokenInfo.setE(endTime);
+ } else {
+ // duration = 0 means a token is valid without a time limitation
+ rawTokenInfo.setE(0L);
+ }
+
+ // There is an upper limit for the size of the token info, skip providers which exceeds this limit
+ String json = Utility.toPrettyJson(null, rawTokenInfo);
+ if (json == null) {
+ log.error("RawTokenInfo serialization failed. Skipped provider.");
+ continue;
+ }
+ System.out.println("Raw token info: ");
+ System.out.println(json);
+ if (json.length() > 244) {
+ tokens.add(null);
+ log.error("ArrowheadToken exceeded the size limit. Skipped provider.");
+ continue;
+ }
+
+ // Finally, generate the token and signature strings
+ try {
+ cipher.init(Cipher.ENCRYPT_MODE, key);
+ byte[] tokenBytes = cipher.doFinal(json.getBytes(StandardCharsets.UTF_8));
+ System.out.println("Token bytes: " + Arrays.toString(tokenBytes));
+ signature.update(tokenBytes);
+ byte[] sigBytes = signature.sign();
+ System.out.println("Signature bytes: " + Arrays.toString(sigBytes));
+
+ String tokenString = Base64.getEncoder().encodeToString(tokenBytes);
+ String signatureString = Base64.getEncoder().encodeToString(sigBytes);
+ tokens.add(new ArrowheadToken(tokenString, signatureString));
+ } catch (Exception e) {
+ e.printStackTrace();
+ log.error("Cipher or Signature class throws public key specific exception: " + e.getMessage());
+ tokens.add(null);
+ }
+ }
+
+ // Throw an exception if none of the token generation was successful
+ boolean nonNullTokenExists = false;
+ for (ArrowheadToken token : tokens) {
+ if (token != null) {
+ nonNullTokenExists = true;
+ break;
+ }
+ }
+ if (!nonNullTokenExists) {
+ log.error("None of the provider ArrowheadSystems in this orchestration have a valid RSA public key spec stored in the database.");
+ throw new ArrowheadException("Token generation failed for all the provider ArrowheadSystems.", Status.INTERNAL_SERVER_ERROR.getStatusCode());
+ }
+
+ return tokens;
+ }
+
+
+ private static List getProviderPublicKeys(List providers) {
+ List keys = new ArrayList<>();
+
+ for (ArrowheadSystem provider : providers) {
+ try {
+ PublicKey key = SecurityUtils.getPublicKey(provider.getAuthenticationInfo(), false);
+ keys.add(key);
+ } catch (AuthException e) {
+ log.error("The stored auth info for the ArrowheadSystem (" + provider.getSystemName()
+ + ") is not a proper RSA public key spec, or it is incorrectly encoded, or missing. The public key can not be decoded from "
+ + "it.");
+ keys.add(null);
+ }
+ }
+
+ // Throw an exception if none of the public keys could be acquired from the specs
+ boolean nonNullKeyExists = false;
+ for (PublicKey key : keys) {
+ if (key != null) {
+ nonNullKeyExists = true;
+ break;
+ }
+ }
+ if (!nonNullKeyExists) {
+ log.error("None of the provider ArrowheadSystems in this orchestration have a valid RSA public key spec stored in the database.");
+ throw new ArrowheadException("Token generation failed for all the provider ArrowheadSystems.", Status.INTERNAL_SERVER_ERROR.getStatusCode());
+ }
+
+ return keys;
+ }
+
+}
\ No newline at end of file
diff --git a/authorization/src/main/java/eu/arrowhead/core/authorization/filter/AuthACF.java b/authorization/src/main/java/eu/arrowhead/core/authorization/filter/AuthACF.java
new file mode 100644
index 00000000..315f9d8f
--- /dev/null
+++ b/authorization/src/main/java/eu/arrowhead/core/authorization/filter/AuthACF.java
@@ -0,0 +1,47 @@
+/*
+ * This work is part of the Productive 4.0 innovation project, which receives grants from the
+ * European Commissions H2020 research and innovation programme, ECSEL Joint Undertaking
+ * (project no. 737459), the free state of Saxony, the German Federal Ministry of Education and
+ * national funding authorities from involved countries.
+ */
+
+package eu.arrowhead.core.authorization.filter;
+
+import eu.arrowhead.common.filter.AccessControlFilter;
+import eu.arrowhead.common.misc.SecurityUtils;
+import eu.arrowhead.core.authorization.AuthorizationMain;
+import javax.annotation.Priority;
+import javax.ws.rs.Priorities;
+import javax.ws.rs.ext.Provider;
+
+@Provider
+@Priority(Priorities.AUTHORIZATION) //2nd highest priority constant, this filter gets executed after the SecurityFilter
+public class AuthACF extends AccessControlFilter {
+
+ @Override
+ public boolean isClientAuthorized(String clientCN, String method, String requestTarget, String requestJson) {
+ if (!SecurityUtils.isKeyStoreCNArrowheadValid(clientCN)) {
+ log.info(clientCN + " is not valid common name, access denied!");
+ return false;
+ }
+
+ String serverCN = (String) configuration.getProperty("server_common_name");
+ String[] serverFields = serverCN.split("\\.", 2);
+
+ if (AuthorizationMain.enableAuthForCloud) {
+ if (!requestTarget.contains("mgmt") || (requestTarget.endsWith("intracloud") && method.equalsIgnoreCase("post"))) {
+ String[] clientFields = clientCN.split("\\.", 2);
+ return serverFields[1].equalsIgnoreCase(clientFields[1]);
+ } else {
+ return clientCN.equalsIgnoreCase("sysop." + serverFields[1]);
+ }
+ } else {
+ if (requestTarget.contains("mgmt")) {
+ return clientCN.equalsIgnoreCase("sysop." + serverFields[1]) || (clientCN.equalsIgnoreCase("certificate_authority." + serverFields[1])
+ && requestTarget.endsWith("publickey") && method.equalsIgnoreCase("get"));
+ } else {
+ return clientCN.equalsIgnoreCase("orchestrator." + serverFields[1]) || clientCN.equalsIgnoreCase("gatekeeper." + serverFields[1]);
+ }
+ }
+ }
+}
diff --git a/authorization/src/main/java/eu/arrowhead/core/authorization/filter/SupportAccessControlFilter.java b/authorization/src/main/java/eu/arrowhead/core/authorization/filter/SupportAccessControlFilter.java
new file mode 100644
index 00000000..ae717a51
--- /dev/null
+++ b/authorization/src/main/java/eu/arrowhead/core/authorization/filter/SupportAccessControlFilter.java
@@ -0,0 +1,77 @@
+/*
+ * This work is part of the Productive 4.0 innovation project, which receives grants from the
+ * European Commissions H2020 research and innovation programme, ECSEL Joint Undertaking
+ * (project no. 737459), the free state of Saxony, the German Federal Ministry of Education and
+ * national funding authorities from involved countries.
+ */
+
+package eu.arrowhead.core.authorization.filter;
+
+import eu.arrowhead.common.Utility;
+import eu.arrowhead.common.exception.AuthException;
+import eu.arrowhead.common.misc.SecurityUtils;
+import eu.arrowhead.core.authorization.AuthorizationMain;
+import javax.annotation.Priority;
+import javax.ws.rs.Priorities;
+import javax.ws.rs.container.ContainerRequestContext;
+import javax.ws.rs.container.ContainerRequestFilter;
+import javax.ws.rs.core.Configuration;
+import javax.ws.rs.core.Context;
+import javax.ws.rs.core.Response.Status;
+import javax.ws.rs.core.SecurityContext;
+import org.apache.log4j.Logger;
+
+//Legacy version of the AuthACF, with certificates containing the system group field
+//@Provider Uncomment this line to activate the filter
+@Priority(Priorities.AUTHORIZATION) //2nd highest priority constant, this filter gets executed after the SecurityFilter
+public class SupportAccessControlFilter implements ContainerRequestFilter {
+
+ private static final Logger log = Logger.getLogger(SupportAccessControlFilter.class.getName());
+ @Context
+ private Configuration configuration;
+
+ @Override
+ public void filter(ContainerRequestContext requestContext) {
+ SecurityContext sc = requestContext.getSecurityContext();
+ String requestTarget = Utility.stripEndSlash(requestContext.getUriInfo().getRequestUri().toString());
+ if (sc.isSecure() && !isGetItCalled(requestContext.getMethod(), requestTarget)) {
+ String commonName = SecurityUtils.getCertCNFromSubject(sc.getUserPrincipal().getName());
+ if (isClientAuthorized(commonName, requestTarget)) {
+ log.info("SSL identification is successful! Cert: " + commonName);
+ } else {
+ log.error(commonName + " is unauthorized to access " + requestTarget);
+ throw new AuthException(commonName + " is unauthorized to access " + requestTarget, Status.UNAUTHORIZED.getStatusCode());
+ }
+ }
+ }
+
+ private boolean isGetItCalled(String method, String requestTarget) {
+ return method.equals("GET") && (requestTarget.endsWith("authorization") || requestTarget.endsWith("mgmt"));
+ }
+
+ private boolean isClientAuthorized(String clientCN, String requestTarget) {
+ String serverCN = (String) configuration.getProperty("server_common_name");
+
+ if (!SecurityUtils.isKeyStoreCNArrowheadValidLegacy(clientCN)) {
+ log.info("Client cert does not have 6 parts, so the access will be denied.");
+ return false;
+ }
+
+ String[] serverFields = serverCN.split("\\.", 2);
+ // serverFields contains: coreSystemName, cloudName.operator.arrowhead.eu
+ if (requestTarget.contains("mgmt")) {
+ // Only the local System Operator can use these methods
+ return clientCN.equalsIgnoreCase("sysop." + serverFields[1]);
+ } else {
+ // If this property is true, then every system from the local cloud can use the auth services
+ if (AuthorizationMain.enableAuthForCloud) {
+ String[] clientFields = clientCN.split("\\.", 2);
+ return serverFields[1].equalsIgnoreCase(clientFields[1]);
+ }
+ // If it is not true, only the Orchestrator and Gatekeeper can use it
+ else {
+ return clientCN.equalsIgnoreCase("orchestrator." + serverFields[1]) || clientCN.equalsIgnoreCase("gatekeeper." + serverFields[1]);
+ }
+ }
+ }
+}
diff --git a/authorization/src/main/java/eu/arrowhead/core/authorization/opcua/AddSystemToAuthorized.java b/authorization/src/main/java/eu/arrowhead/core/authorization/opcua/AddSystemToAuthorized.java
new file mode 100644
index 00000000..2bf3b70a
--- /dev/null
+++ b/authorization/src/main/java/eu/arrowhead/core/authorization/opcua/AddSystemToAuthorized.java
@@ -0,0 +1,66 @@
+package eu.arrowhead.core.authorization.opcua;
+
+import java.io.IOException;
+
+import javax.ws.rs.core.Response;
+
+import org.eclipse.milo.opcua.sdk.core.ValueRanks;
+import org.eclipse.milo.opcua.sdk.server.api.methods.AbstractMethodInvocationHandler;
+import org.eclipse.milo.opcua.sdk.server.nodes.UaMethodNode;
+import org.eclipse.milo.opcua.stack.core.Identifiers;
+import org.eclipse.milo.opcua.stack.core.UaException;
+import org.eclipse.milo.opcua.stack.core.types.builtin.LocalizedText;
+import org.eclipse.milo.opcua.stack.core.types.builtin.Variant;
+import org.eclipse.milo.opcua.stack.core.types.structured.Argument;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import com.fasterxml.jackson.core.JsonParseException;
+import com.fasterxml.jackson.databind.JsonMappingException;
+
+import eu.arrowhead.common.opcua.OpcUaHelper;
+import eu.arrowhead.core.authorization.AuthorizationApi;
+
+public class AddSystemToAuthorized extends AbstractMethodInvocationHandler{
+ private final Logger logger = LoggerFactory.getLogger(getClass());
+
+ public AddSystemToAuthorized(UaMethodNode node) {
+ super(node);
+ }
+
+ public static final Argument IntraCloudAuthEntry = new Argument("IntraCloudAuthEntry", Identifiers.String, ValueRanks.Scalar, null,
+ new LocalizedText("IntraCloudAuthEntry"));
+
+ public static final Argument ResponseStatus = new Argument("ResponseStatus", Identifiers.String, ValueRanks.Scalar, null,
+ new LocalizedText("ResponseStatus"));
+
+ @Override
+ public Argument[] getInputArguments() {
+ return new Argument[] { IntraCloudAuthEntry };
+ }
+
+ @Override
+ public Argument[] getOutputArguments() {
+ return new Argument[] { ResponseStatus };
+ }
+
+ @Override
+ protected Variant[] invoke(InvocationContext invocationContext, Variant[] inputValues) throws UaException {
+ Response out = null;
+ logger.debug("Invoking query() method of Object '{}'", invocationContext.getObjectId());
+ try {
+ out = new AuthorizationApi().addSystemToAuthorizedGeneric(
+ new OpcUaHelper().icaeFromJsonString(inputValues[0].getValue().toString()));
+ } catch (JsonParseException e) {
+ e.printStackTrace();
+ } catch (JsonMappingException e) {
+ e.printStackTrace();
+ } catch (IOException e) {
+ e.printStackTrace();
+ }
+
+ String res = out.getStatusInfo().getReasonPhrase();
+
+ return new Variant[] { new Variant(res) };
+ }
+}
diff --git a/authorization/src/main/resources/hibernate.cfg.xml b/authorization/src/main/resources/hibernate.cfg.xml
new file mode 100644
index 00000000..9cf7443b
--- /dev/null
+++ b/authorization/src/main/resources/hibernate.cfg.xml
@@ -0,0 +1,41 @@
+
+
+
+
+
+
+ true
+ update
+
+ org.hibernate.hikaricp.internal.HikariCPConnectionProvider
+ 10
+ true
+ 250
+ 2048
+ true
+ true
+ true
+ true
+ true
+ true
+ true
+ false
+ Authorization
+ true
+
+
+
+
+
+
+
+
+
+
diff --git a/authorization/src/main/resources/openapi.yaml b/authorization/src/main/resources/openapi.yaml
new file mode 100644
index 00000000..b39d8dbd
--- /dev/null
+++ b/authorization/src/main/resources/openapi.yaml
@@ -0,0 +1,7 @@
+prettyPrint: true
+openAPI:
+ info:
+ title: Arrowhead Authorization Core System API
+ description: 'This page shows the REST interfaces offered by the Authorization Core System.'
+ contact:
+ url: https://github.com/arrowhead-f
\ No newline at end of file
diff --git a/certificates/broker/broker.crt b/certificates/broker/broker.crt
new file mode 100644
index 00000000..238ef3f6
--- /dev/null
+++ b/certificates/broker/broker.crt
@@ -0,0 +1,18 @@
+-----BEGIN CERTIFICATE-----
+MIIC+DCCAeCgAwIBAgIEWlYfrjANBgkqhkiG9w0BAQsFADBHMSEwHwYDVQQKDBhB
+SVRJQSBJbnRlcm5hdGlvbmFsIEluYy4xCzAJBgNVBAYTAkhVMRUwEwYDVQQDDAxh
+cnJvd2hlYWQuZXUwHhcNMTgwMTEwMTQxNDA2WhcNMjgwMTEwMTQxNDA2WjA1MQsw
+CQYDVQQGEwJIVTEmMCQGA1UEAwwddGVzdGJyb2tlci5haXRpYS5hcnJvd2hlYWQu
+ZXUwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDRkYEc6KdAXmy1gvuS
+u4u6MIJbS9JlkdtXnsP4G/HWFx3LuObIAMzbBw3NOYCIhhFwVxjvf0ME0gTA+llw
+7zdY+7dUj+TJ3EBtfgvajfHm2IO71S1kZTCCTfFdbbtXMj8uf7hCEFLvKM4GUNe6
+i368xkVh5eBihCbm/F77jKt/tV/K73NB91dJBC290RJjrkq0mj5Hs4+WY1ezX/B1
+XR1iOzjs6ZmL5gxh1A7PqQHkbL7/Qotos3qzHUIqzUR1QlpJYgS/fjZfrLoJvrfq
+WvKsQ8sD0y5wxdO1QXOt2EDA0SUUjkJDkePujUsU5ljoXEOgYLGVQJoz+MGVrH4r
+SFepAgMBAAEwDQYJKoZIhvcNAQELBQADggEBAEJUFWWWMMNnogOYiWdH4rUNVESw
+8rj1oczkZg+h+oQV4Qg6GxXFr9qL5LUOlcDRalJbWjd8yJBtQDIT7A2AuCQjLocg
+F1FZDa8nWcPkYNr7h4QX7E/7PNqAghjARSVaycMDtqaVCB0RlmcYMjreFjM71kRf
+HNcMOKLpdIMPhpfr2MC8E7EG6zfK0zsN3+qgZizqfR7Q8f6S0T8srIMuvBjk2h1a
+iM13ftu1/cn/d2RMAom46Mh1Z3qwhucO58BMwHzHJX24UE9xGWgOW3u/OrHNMmhz
+mNvlTNpbu3hZWoVMKnkWM3PVgGJSSJ2LlMBqO3uaOo1rdQ9WwPORST/Urgc=
+-----END CERTIFICATE-----
diff --git a/certificates/broker/broker.key b/certificates/broker/broker.key
new file mode 100644
index 00000000..4778f284
--- /dev/null
+++ b/certificates/broker/broker.key
@@ -0,0 +1,32 @@
+Bag Attributes
+ friendlyName: testbroker.aitia.arrowhead.eu
+ localKeyID: 54 69 6D 65 20 31 35 31 35 35 39 35 38 38 31 30 30 35
+Key Attributes:
+-----BEGIN PRIVATE KEY-----
+MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQDRkYEc6KdAXmy1
+gvuSu4u6MIJbS9JlkdtXnsP4G/HWFx3LuObIAMzbBw3NOYCIhhFwVxjvf0ME0gTA
++llw7zdY+7dUj+TJ3EBtfgvajfHm2IO71S1kZTCCTfFdbbtXMj8uf7hCEFLvKM4G
+UNe6i368xkVh5eBihCbm/F77jKt/tV/K73NB91dJBC290RJjrkq0mj5Hs4+WY1ez
+X/B1XR1iOzjs6ZmL5gxh1A7PqQHkbL7/Qotos3qzHUIqzUR1QlpJYgS/fjZfrLoJ
+vrfqWvKsQ8sD0y5wxdO1QXOt2EDA0SUUjkJDkePujUsU5ljoXEOgYLGVQJoz+MGV
+rH4rSFepAgMBAAECggEAASDqiXweMe3hUMqGUiwayXaRrjaVps+1355By+pCE9Ct
+YzNXC+RrnGcmhZxG3VM4TrBNp4pk13fNznu1U5lvy4jhoEQntlRyySyy3YKTyB6b
+LWhxsCK4s8FXbwPnmXA2olu87AWfjLazJETD/B1T7t1jA5mvlalNEBCVu23dkk3R
+HE+X3fknPXMotbnc9Jb6ujPIiAMAXB3lWFMwqeRXA9fqJ06YktjhWvOIIBe1vXRd
+LqzoTSRP24XQdDctaMlmxZjCg+j4w2P5Dw0k1i8FoFWdLj2pjFP0Be4Nmm3z9cjo
+KMNRYpYevYhI+TSCHjx4IytXeza9kP4xNZQxs/u9SQKBgQD7GXvA1wmvyRKER00O
+bR4EHwMcVNb6ZUh2yknWrMwUkBUkKt+Xm+UuFACyuAPjFk95EEsfLxD9YtHfB6vy
+oHhRuyoM5RgX2bAEvka1/vVkAmPnz5U5K66k95U8T04pQO/6A0Tlw/gDbd4Jx5dK
+MMb+v2S/IZ9yioJCG3Ch1Z+6QwKBgQDVqIcB1/UwM6NOGpv9WSlic3eOOQcPfykZ
+U9tCS4/knScM6JFMnf/yo6TVWmZgyhLZiaSCC+2We3kmSHBQ/KCSzvgnJMGzrr/v
+vYpFVTTznZAQYLLw1AHw7eCWL2AHcSFtKzK4QIQfCcHYD5hG2eCH+Eh1e2YsmCIE
+bkmI1snVowKBgQD1wDojQRKk7Yjm15Wt8dN3uxZjSxFf3XKveEoOBe5yjZVzYVaV
+3ijOERuhzoFXZoV/ehfbvEfHhkjbHHoIK0+2xhRgrHMJHK+t5L/17UMjj0dSIBLu
+8jaqE/oGHaPksj5sFXj/2u1IBeZS8fm6zcRknNMwUOrpwm+73Xk9moMKWwKBgCjL
+OaPHeQAQC/EgsYHo4Tbz9vLHvqpdzBoS4b1BsiKjQs57JSrsy+6nAhTeYse+LWUt
+m6Rc5KsjvhjqIc/EvF/rdoP6x6UoC2uZ0+mzAB43ikcmP2QuBoTa1lO2je490nUG
+l3OIlfFbKpC3QYrdlLpBVIxh2DtRfMU9zXp8ECNjAoGBAPRTi4/CiKeNSMV8BGFe
+EXzwLlaB+28TnSM/XW7npkdPkFU9nS7EOW7SsqkyfHFDj+c9JrvBnDGaHPu06Iv9
+Xw4SYer4u91sO6KiDm0fLX9tCzo9MFFH2Rj+GkOHlwkwe2J1Cc9Wdf48JhHCf/dD
+YOk6MsK/prkzSJVtRNr4cViX
+-----END PRIVATE KEY-----
diff --git a/certificates/broker/broker.pem b/certificates/broker/broker.pem
new file mode 100644
index 00000000..238ef3f6
--- /dev/null
+++ b/certificates/broker/broker.pem
@@ -0,0 +1,18 @@
+-----BEGIN CERTIFICATE-----
+MIIC+DCCAeCgAwIBAgIEWlYfrjANBgkqhkiG9w0BAQsFADBHMSEwHwYDVQQKDBhB
+SVRJQSBJbnRlcm5hdGlvbmFsIEluYy4xCzAJBgNVBAYTAkhVMRUwEwYDVQQDDAxh
+cnJvd2hlYWQuZXUwHhcNMTgwMTEwMTQxNDA2WhcNMjgwMTEwMTQxNDA2WjA1MQsw
+CQYDVQQGEwJIVTEmMCQGA1UEAwwddGVzdGJyb2tlci5haXRpYS5hcnJvd2hlYWQu
+ZXUwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDRkYEc6KdAXmy1gvuS
+u4u6MIJbS9JlkdtXnsP4G/HWFx3LuObIAMzbBw3NOYCIhhFwVxjvf0ME0gTA+llw
+7zdY+7dUj+TJ3EBtfgvajfHm2IO71S1kZTCCTfFdbbtXMj8uf7hCEFLvKM4GUNe6
+i368xkVh5eBihCbm/F77jKt/tV/K73NB91dJBC290RJjrkq0mj5Hs4+WY1ezX/B1
+XR1iOzjs6ZmL5gxh1A7PqQHkbL7/Qotos3qzHUIqzUR1QlpJYgS/fjZfrLoJvrfq
+WvKsQ8sD0y5wxdO1QXOt2EDA0SUUjkJDkePujUsU5ljoXEOgYLGVQJoz+MGVrH4r
+SFepAgMBAAEwDQYJKoZIhvcNAQELBQADggEBAEJUFWWWMMNnogOYiWdH4rUNVESw
+8rj1oczkZg+h+oQV4Qg6GxXFr9qL5LUOlcDRalJbWjd8yJBtQDIT7A2AuCQjLocg
+F1FZDa8nWcPkYNr7h4QX7E/7PNqAghjARSVaycMDtqaVCB0RlmcYMjreFjM71kRf
+HNcMOKLpdIMPhpfr2MC8E7EG6zfK0zsN3+qgZizqfR7Q8f6S0T8srIMuvBjk2h1a
+iM13ftu1/cn/d2RMAom46Mh1Z3qwhucO58BMwHzHJX24UE9xGWgOW3u/OrHNMmhz
+mNvlTNpbu3hZWoVMKnkWM3PVgGJSSJ2LlMBqO3uaOo1rdQ9WwPORST/Urgc=
+-----END CERTIFICATE-----
diff --git a/certificates/broker/broker2.crt b/certificates/broker/broker2.crt
new file mode 100644
index 00000000..ef56b882
--- /dev/null
+++ b/certificates/broker/broker2.crt
@@ -0,0 +1,18 @@
+-----BEGIN CERTIFICATE-----
+MIIC7DCCAdSgAwIBAgIEW6DbjjANBgkqhkiG9w0BAQsFADBHMSEwHwYDVQQKDBhB
+SVRJQSBJbnRlcm5hdGlvbmFsIEluYy4xCzAJBgNVBAYTAkhVMRUwEwYDVQQDDAxh
+cnJvd2hlYWQuZXUwHhcNMTgwOTE4MTEwMzQyWhcNMTkwOTE4MTEwMzQyWjApMScw
+JQYDVQQDDB50ZXN0YnJva2VyMi5haXRpYS5hcnJvd2hlYWQuZXUwggEiMA0GCSqG
+SIb3DQEBAQUAA4IBDwAwggEKAoIBAQC35mGViemS7yKtbAKDTWRRU3/9oE8Y5F7a
+gIlTaalYUjGAPMGFeJ/asR4FuCgti0b3oDYHcpK6ficgFfDmhW46ab636188hDvQ
+iiHpsUx85IuHYmsWbjjUtCgnPZwByl4ld7zc3DNPkG0VV5cLZaWdHzpmUOfrG3VZ
+mnfV2a9/tetl9+RBU9KvVonBh7rBToq7HHAhzajdAPtVcIbrBqN82knZp+opObsX
+HP62QsZqqn2z425pBbknUErBhQBJTQ6PqW1ATcGRmnbDo/56qxIGNCQ8Wd5m0z75
+QmnocDsyZiusbZ8wC1yo4vMvybsh7jKYeoEvY6mvgZQ+v9QWjVipAgMBAAEwDQYJ
+KoZIhvcNAQELBQADggEBADCMWO5NgTVglH984VdikMhN0jW/F1spkuw171+6AluB
+plK55p7JzLQK6RyYxam75F6KRc6TdsmwUk3hcPAe3QdmDISoqkQInTdfdavN8LF3
+jLfiEpv4caViZp4KFL3iCID7ygmzNJQOVD89+wbIeDCNCunbPVy3nfv/Q3hpR1x7
+WiV2hMdzZzsfdxnmVobxL/fitGKJEyJuhPoIQVr/cTXewHrve2SrxeD/xr8pORP9
+eISyelMqkDop/LpBnUyzSkYD0VuQ4IZDSlBZTLSIhe3/KDCIrmKjxUf2+QoHaksO
+6aVWfeW917UQaMab3VkHeL9Si7OC+yQojBjt76Ye2No=
+-----END CERTIFICATE-----
diff --git a/certificates/broker/broker2.key b/certificates/broker/broker2.key
new file mode 100644
index 00000000..2cd619aa
--- /dev/null
+++ b/certificates/broker/broker2.key
@@ -0,0 +1,31 @@
+Bag Attributes
+ friendlyName: testbroker2.aitia.arrowhead.eu (arrowhead.eu)
+ localKeyID: 54 69 6D 65 20 31 35 33 37 32 37 32 38 37 34 32 38 37
+Key Attributes:
+-----BEGIN RSA PRIVATE KEY-----
+MIIEpAIBAAKCAQEAt+ZhlYnpku8irWwCg01kUVN//aBPGORe2oCJU2mpWFIxgDzB
+hXif2rEeBbgoLYtG96A2B3KSun4nIBXw5oVuOmm+t+tfPIQ70Ioh6bFMfOSLh2Jr
+Fm441LQoJz2cAcpeJXe83NwzT5BtFVeXC2WlnR86ZlDn6xt1WZp31dmvf7XrZffk
+QVPSr1aJwYe6wU6KuxxwIc2o3QD7VXCG6wajfNpJ2afqKTm7Fxz+tkLGaqp9s+Nu
+aQW5J1BKwYUASU0Oj6ltQE3BkZp2w6P+eqsSBjQkPFneZtM++UJp6HA7MmYrrG2f
+MAtcqOLzL8m7Ie4ymHqBL2Opr4GUPr/UFo1YqQIDAQABAoIBAE1F3udJBf6ZwUaB
+xiSKFocdwQA/pjzdaQYDULDeCyxzjR1HakG9R95K4kSg9zD7/xv6E/nZKNJ4Q9R1
+o74ZfxHIcT0zB9eT8NZV1J4vw+YO2ppOvpS0R+kIsn6aor0V3a18vL1JQb5C/7Mi
+FPPTCF4XSyDHCkx+QzcdcxFG4TmV7qh3021TpLT5BAf+DSwy9W/xUsgttC039Q1C
+lLAgMIUheOTUQhiapvL67F0iepE2Gms4RjYzbrfVzmJCeTmR4r7btOaGwQAUfeSO
+STaMUW7rlyS6j8cHX2ekeEfCcg+FA0amI2Uyb39+UKQujYs9tt2AqabeVumi3ErA
+KylW7kkCgYEA6oWmCjor+MXpCEDclqIrQgYZiytA8OpJbPuALi44+AIVPbdnipcW
+1lF1SmOWHAmBLNpiNwgDCFWpH7E2nSt72WuDLv3R5f1Lgx/TO3fk09erfcCynJ5O
+AmRcuriLbG60npzUggyq4kSuzNfJ9WuBMAAcwWjIjULjPSZD/MmRj+sCgYEAyL3m
+iWs142NlpoeMBmiTspU6y5bGdqlLRViMPsHaNrmcxp0ZlK57nwQ4UqfzTRqYvnos
+szzZ9vAjyqk4scyrVO1tb6Oh/s3ppDi9WnUfYB7CAe+/YqPYaljOtGjRNn4EfBbR
+Q0JMOXLRs4fO0iJhdpUZK1Z0wbr4KUTN0O2VqLsCgYEA2NrsRdi0gDzA1ljjoIYj
+6dEqHX3JdvutGZEVXZ5wHyQN8Bt/Kq+69kIsarTbQOGpCIiG+Mv3weUzlZKQJZTn
+2IuJqhIZ3Mjy4jSzZfHfLePnN0jP3/Khv6R9+kpa4dEcQu5Rsk90U/WejNuca8sU
+kgYIkO8LQ1PuO8T+aOkaKDECgYEAsSKRGVWXWa1N92LIdqc1HXJ9TUUgn3jHyMtY
+BdC9I2kqJEBgYEiPIURyBs3Y7YK4oevhIdBibXp9uskUFCCMVwajeM2llthQgO7o
+d7e+Fe1OOLM76Gi4AS+YRq3vXuxYPL9NrNIFvPPKg2clY1ufMSpj6VAC+yhm7tOd
+4eykER0CgYA97QokT1rNiQOeDEeKQDZb612RXCr4dsNdwK/fAAjh9zvoixHVmnpT
+6nCfhUfmGweV5q4s9xtF1wPBjWctiNoEi5lX+hzkhAPHpgVVwOx6VBovJ0H5Otw6
+7ILMg+QDXw6I6lzt1ryDbvvI112OJdc8jbD84SdXrZmCyj3EBscYLg==
+-----END RSA PRIVATE KEY-----
diff --git a/certificates/broker/broker2.pem b/certificates/broker/broker2.pem
new file mode 100644
index 00000000..ef56b882
--- /dev/null
+++ b/certificates/broker/broker2.pem
@@ -0,0 +1,18 @@
+-----BEGIN CERTIFICATE-----
+MIIC7DCCAdSgAwIBAgIEW6DbjjANBgkqhkiG9w0BAQsFADBHMSEwHwYDVQQKDBhB
+SVRJQSBJbnRlcm5hdGlvbmFsIEluYy4xCzAJBgNVBAYTAkhVMRUwEwYDVQQDDAxh
+cnJvd2hlYWQuZXUwHhcNMTgwOTE4MTEwMzQyWhcNMTkwOTE4MTEwMzQyWjApMScw
+JQYDVQQDDB50ZXN0YnJva2VyMi5haXRpYS5hcnJvd2hlYWQuZXUwggEiMA0GCSqG
+SIb3DQEBAQUAA4IBDwAwggEKAoIBAQC35mGViemS7yKtbAKDTWRRU3/9oE8Y5F7a
+gIlTaalYUjGAPMGFeJ/asR4FuCgti0b3oDYHcpK6ficgFfDmhW46ab636188hDvQ
+iiHpsUx85IuHYmsWbjjUtCgnPZwByl4ld7zc3DNPkG0VV5cLZaWdHzpmUOfrG3VZ
+mnfV2a9/tetl9+RBU9KvVonBh7rBToq7HHAhzajdAPtVcIbrBqN82knZp+opObsX
+HP62QsZqqn2z425pBbknUErBhQBJTQ6PqW1ATcGRmnbDo/56qxIGNCQ8Wd5m0z75
+QmnocDsyZiusbZ8wC1yo4vMvybsh7jKYeoEvY6mvgZQ+v9QWjVipAgMBAAEwDQYJ
+KoZIhvcNAQELBQADggEBADCMWO5NgTVglH984VdikMhN0jW/F1spkuw171+6AluB
+plK55p7JzLQK6RyYxam75F6KRc6TdsmwUk3hcPAe3QdmDISoqkQInTdfdavN8LF3
+jLfiEpv4caViZp4KFL3iCID7ygmzNJQOVD89+wbIeDCNCunbPVy3nfv/Q3hpR1x7
+WiV2hMdzZzsfdxnmVobxL/fitGKJEyJuhPoIQVr/cTXewHrve2SrxeD/xr8pORP9
+eISyelMqkDop/LpBnUyzSkYD0VuQ4IZDSlBZTLSIhe3/KDCIrmKjxUf2+QoHaksO
+6aVWfeW917UQaMab3VkHeL9Si7OC+yQojBjt76Ye2No=
+-----END CERTIFICATE-----
diff --git a/certificates/broker/broker2_cert.jks b/certificates/broker/broker2_cert.jks
new file mode 100644
index 00000000..5e2fb54e
Binary files /dev/null and b/certificates/broker/broker2_cert.jks differ
diff --git a/certificates/broker/broker_cert.jks b/certificates/broker/broker_cert.jks
new file mode 100644
index 00000000..dd1d780b
Binary files /dev/null and b/certificates/broker/broker_cert.jks differ
diff --git a/certificates/broker/master_arrowhead_cert.pem b/certificates/broker/master_arrowhead_cert.pem
new file mode 100644
index 00000000..66e5b00f
--- /dev/null
+++ b/certificates/broker/master_arrowhead_cert.pem
@@ -0,0 +1,19 @@
+-----BEGIN CERTIFICATE-----
+MIIDITCCAgmgAwIBAgIJAIiy+gkhV/z6MA0GCSqGSIb3DQEBCwUAMEcxITAfBgNV
+BAoMGEFJVElBIEludGVybmF0aW9uYWwgSW5jLjELMAkGA1UEBhMCSFUxFTATBgNV
+BAMMDGFycm93aGVhZC5ldTAeFw0xODAxMTAxMjM1MTFaFw0yODAxMDgxMjM1MTFa
+MEcxITAfBgNVBAoMGEFJVElBIEludGVybmF0aW9uYWwgSW5jLjELMAkGA1UEBhMC
+SFUxFTATBgNVBAMMDGFycm93aGVhZC5ldTCCASIwDQYJKoZIhvcNAQEBBQADggEP
+ADCCAQoCggEBAMMLdPYaRBqhIQFaIaq4icCKTGeviBYl8UDK4hWarO6Qrp1twh5I
+6JLKEjpu4bfsAcF/q6ExuOWOdQVOWqxnHIvLK2CxTWMf14TH1W08sLExidr/7LyE
+cgBKzlVBwBaLrrU0jPHsMeMgCaUfQJJ2dEiSNXD8uSeUON+uIA5K0P4LqVfLELjr
+IN3oIMhUxzICmxFx+Ek0gdi8PsFaOKnYH0OOginXY2EtMkWSvTwZpEmdZVkm229m
+MgDylBeqLWTonawCO2tbTSm0rJgDpfCiST9Zf2Ppu6zJ1H0BPTcT0/hvXSnZnkxF
+dEWTSRV5xstCHn5VOGv4JdSB7T06J4uhTwsCAwEAAaMQMA4wDAYDVR0TBAUwAwEB
+/zANBgkqhkiG9w0BAQsFAAOCAQEAiG/hAl69bJYeqFbi5lUElmCWHGatCnAz2zAj
+ddGi8DHL3sEDgzSYy7UUiDKEhJvLOoJwq+m7RfCr3LyEXCCw3F0CLMAfXa+u5ZmQ
+sNJiz43dvZ/TvBxMuyZNbqzspVVD0t8li+bmDMqAjVVfgmtV9fVygBMYlLzCmjvF
+3ZmkU+1yuQhgTMe8/WY90vaMzB7pciScaVXVjF/AcCWu8NC6cG6rTbRtBUVgNb4I
+NuHTc72XfbgvhGeVGstpvnJlFqCUvWprgQeglJkZSxtgmrwUC1PSzeUPlIbuKhuy
+i+ZCHmiIPrBNMbwm56IspNVHwrDYC9BAm2MEn0VKgm0YdMrXyg==
+-----END CERTIFICATE-----
diff --git a/certificates/certificate_password.txt b/certificates/certificate_password.txt
new file mode 100644
index 00000000..29de3ca4
--- /dev/null
+++ b/certificates/certificate_password.txt
@@ -0,0 +1,3 @@
+PASSWORD: 123456
+
+Valid for all certificate files in this folder.
\ No newline at end of file
diff --git a/certificates/dev_notes.txt b/certificates/dev_notes.txt
new file mode 100644
index 00000000..17642587
--- /dev/null
+++ b/certificates/dev_notes.txt
@@ -0,0 +1,77 @@
+How to create Arrowhead-compliant certificates
+
+0. Take the certificate keystore that is wished to be used as the signer
+
+1. Open it in KeyStore Explorer (http://keystore-explorer.org/downloads.html)
+ - default KeyStore and Key pwd-s for all test certificates: '12345'
+
+2. Right click on the certificate itself --> Sign / Sign New Key Pair
+
+3. Edit settings for the new certificate:
+ - validity period: e.g. set to 10 years
+ - Name --> There is a book icon with '@': set CN fields
+ CN: ...arrowhead.eu format
+ Other fields can be filled out arbitrarily.
+
+4. Move the newly created certificate to a new tab within the app
+ - drag & drop works
+ - it creates a new jks file for it
+ - 'Save as' this new keystore as a '.jks' file
+
+5. Tools --> Set Keystore Password
+
+6. Right click on the certificate --> Set Password
+ - the 'old' password is empty
+ - give a new password to the certificate itself
+
+7. Tools --> Set Keystore Type --> JKS
+ - this changes from 'p12' filetype to Java Keystore format
+
+8. Save the keystore again (File --> Save or CTRL+S)
+
++1. Don't forget to delete the new certificate from the old one's keystore!
+
+---------------------------------------------------------------------------
+How to extract crt and key files from jks files: (run as sudo/administrator)
+
+1. Get the alias from .jks file
+keytool -v -list -keystore .jks
+
+2. Export the .der file
+keytool -export -alias "alias with the () part included" -file sample.der -keystore my.jks
+
+3.Convert the .der file to unencrypted PEM (crt file)
+openssl x509 -inform der -in sample.der -out sample.crt
+
+4.Export the .p12 file
+keytool -importkeystore -srckeystore my.jks -destkeystore keystore.p12 -deststoretype PKCS12 -destkeypass pass
+
+5.Convert the .p12 file to unencrypted PEM (key file)
+openssl pkcs12 -in keystore.p12 -nodes -nocerts -out server.key
+
+---------------------------------------------------------------------------
+How to export certificate public key in Base64 with the ArrowheadProvider client skeleton:
+1. Edit the app.config file with the proper client and trust store keystores
+2. Start the skeleton with "-tls" command line arguments.
+3. The skeleton will print out the Base64 encoded string on the console during start. (This is the 2nd print, which starts as: "Server PublicKey Base64: ")
+
+---------------------------------------------------------------------------
+How to export certificate public key in Base64 for the database manually:
+1. Open the jks in Keystore Explorer
+2. Right click on certificate
+3. Export -> Export Public Key
+4. Remove --BEGIN-- and --END-- from .openssl file
+5. Insert into Auth_info field in Arrowhead database
+---------------------------------------------------------------------------
+
+How to add our root certificate to the JDK
+1. Go to the ~/jdk1.8.0_92/jre/bin
+2. Copy the master_arrowhead_cert.crt here
+3. Open a command line as administrator here
+4. Type: keytool -import -alias "arrowhead.eu" -keystore "$JAVA_HOME/lib/security/cacerts" -file master_arrowhead_cert.crt
+ (JAVA_HOME can be the JRE or JDK/jre/lib version of cacert, used truststore may depend on individual setup)
+5. The default password is "changeit", then type "y" and hit enter. The certificate is added to the JDK truststore now.
+
+How to delete an alias from java cacerts keystore
+1. keytool -delete -alias "aliasname" -keystore "file_path_to_cacerts"
+ - password: changeit
\ No newline at end of file
diff --git a/certificates/master.crt b/certificates/master.crt
new file mode 100644
index 00000000..66e5b00f
--- /dev/null
+++ b/certificates/master.crt
@@ -0,0 +1,19 @@
+-----BEGIN CERTIFICATE-----
+MIIDITCCAgmgAwIBAgIJAIiy+gkhV/z6MA0GCSqGSIb3DQEBCwUAMEcxITAfBgNV
+BAoMGEFJVElBIEludGVybmF0aW9uYWwgSW5jLjELMAkGA1UEBhMCSFUxFTATBgNV
+BAMMDGFycm93aGVhZC5ldTAeFw0xODAxMTAxMjM1MTFaFw0yODAxMDgxMjM1MTFa
+MEcxITAfBgNVBAoMGEFJVElBIEludGVybmF0aW9uYWwgSW5jLjELMAkGA1UEBhMC
+SFUxFTATBgNVBAMMDGFycm93aGVhZC5ldTCCASIwDQYJKoZIhvcNAQEBBQADggEP
+ADCCAQoCggEBAMMLdPYaRBqhIQFaIaq4icCKTGeviBYl8UDK4hWarO6Qrp1twh5I
+6JLKEjpu4bfsAcF/q6ExuOWOdQVOWqxnHIvLK2CxTWMf14TH1W08sLExidr/7LyE
+cgBKzlVBwBaLrrU0jPHsMeMgCaUfQJJ2dEiSNXD8uSeUON+uIA5K0P4LqVfLELjr
+IN3oIMhUxzICmxFx+Ek0gdi8PsFaOKnYH0OOginXY2EtMkWSvTwZpEmdZVkm229m
+MgDylBeqLWTonawCO2tbTSm0rJgDpfCiST9Zf2Ppu6zJ1H0BPTcT0/hvXSnZnkxF
+dEWTSRV5xstCHn5VOGv4JdSB7T06J4uhTwsCAwEAAaMQMA4wDAYDVR0TBAUwAwEB
+/zANBgkqhkiG9w0BAQsFAAOCAQEAiG/hAl69bJYeqFbi5lUElmCWHGatCnAz2zAj
+ddGi8DHL3sEDgzSYy7UUiDKEhJvLOoJwq+m7RfCr3LyEXCCw3F0CLMAfXa+u5ZmQ
+sNJiz43dvZ/TvBxMuyZNbqzspVVD0t8li+bmDMqAjVVfgmtV9fVygBMYlLzCmjvF
+3ZmkU+1yuQhgTMe8/WY90vaMzB7pciScaVXVjF/AcCWu8NC6cG6rTbRtBUVgNb4I
+NuHTc72XfbgvhGeVGstpvnJlFqCUvWprgQeglJkZSxtgmrwUC1PSzeUPlIbuKhuy
+i+ZCHmiIPrBNMbwm56IspNVHwrDYC9BAm2MEn0VKgm0YdMrXyg==
+-----END CERTIFICATE-----
diff --git a/certificates/master.p12 b/certificates/master.p12
new file mode 100644
index 00000000..daf24358
Binary files /dev/null and b/certificates/master.p12 differ
diff --git a/certificates/testcloud1/authorization.p12 b/certificates/testcloud1/authorization.p12
new file mode 100644
index 00000000..bcb8f5a4
Binary files /dev/null and b/certificates/testcloud1/authorization.p12 differ
diff --git a/certificates/testcloud1/authorization.pub b/certificates/testcloud1/authorization.pub
new file mode 100644
index 00000000..493666c8
--- /dev/null
+++ b/certificates/testcloud1/authorization.pub
@@ -0,0 +1,9 @@
+-----BEGIN PUBLIC KEY-----
+MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEApOc3Yju2sak4Y/gkYihw
+ajmTVCyx+DMUiuo1GX6AbrcrOjLqbdgHM5YFk31tCfRX3kjF/IS70GchZ9Qq1t/M
+Ixu65Uf0Y7M7+mv27WcbgzvUY7mq6YrFgNp8kuS/A8QSa724cS2krRdk477kRqOi
+F/h6aEWUSNNJlg8NkcA/EK8DiVvPaMAqNNY/eFgRvCSmhBo2iwwt5TxP2pftZ5DW
+zXsogsLlb7bWIm79/cdxC+xeLRMt7lty8fM/Cyq3RKn5k8ldUp5396hxaM3MqBok
+lSXNQeIMOzor5YIdlDLhyHmg1Oxe5oXTjpVagn1W8dUPCoPYnjvUhUZ/4ue9XJuQ
+LQIDAQAB
+-----END PUBLIC KEY-----
diff --git a/certificates/testcloud1/event_handler.p12 b/certificates/testcloud1/event_handler.p12
new file mode 100644
index 00000000..b7336e1c
Binary files /dev/null and b/certificates/testcloud1/event_handler.p12 differ
diff --git a/certificates/testcloud1/gatekeeper.p12 b/certificates/testcloud1/gatekeeper.p12
new file mode 100644
index 00000000..87782d2f
Binary files /dev/null and b/certificates/testcloud1/gatekeeper.p12 differ
diff --git a/certificates/testcloud1/gateway.p12 b/certificates/testcloud1/gateway.p12
new file mode 100644
index 00000000..e75ab482
Binary files /dev/null and b/certificates/testcloud1/gateway.p12 differ
diff --git a/certificates/testcloud1/orchestrator.p12 b/certificates/testcloud1/orchestrator.p12
new file mode 100644
index 00000000..2911609d
Binary files /dev/null and b/certificates/testcloud1/orchestrator.p12 differ
diff --git a/certificates/testcloud1/service_registry_sql.p12 b/certificates/testcloud1/service_registry_sql.p12
new file mode 100644
index 00000000..04eda620
Binary files /dev/null and b/certificates/testcloud1/service_registry_sql.p12 differ
diff --git a/certificates/testcloud1/testcloud1.p12 b/certificates/testcloud1/testcloud1.p12
new file mode 100644
index 00000000..c981877b
Binary files /dev/null and b/certificates/testcloud1/testcloud1.p12 differ
diff --git a/certificates/testcloud1/truststore.p12 b/certificates/testcloud1/truststore.p12
new file mode 100644
index 00000000..aa386178
Binary files /dev/null and b/certificates/testcloud1/truststore.p12 differ
diff --git a/certificates/testcloud2/authorization.p12 b/certificates/testcloud2/authorization.p12
new file mode 100644
index 00000000..2df97ce5
Binary files /dev/null and b/certificates/testcloud2/authorization.p12 differ
diff --git a/certificates/testcloud2/authorization.pub b/certificates/testcloud2/authorization.pub
new file mode 100644
index 00000000..55265b07
--- /dev/null
+++ b/certificates/testcloud2/authorization.pub
@@ -0,0 +1,9 @@
+-----BEGIN PUBLIC KEY-----
+MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAmIyFSDfvJu4JuNtloPnA
+jHSjE4xkkPWCVPUpmK0h9GNdwMRC0WeR1orXp3a8CBjB7f6kpTA3OsdaOuzdfdLt
+0zhzPRHa7mSM/4UYwy+/5sp49sPCjIutDBIk3X0NSAAp7g859sSVoVjCBo358z83
+QrQ+049IPk9sYYQL8CCfLgITAlnUnx2oos++oMlvXyiekHmpmRObIxD6+mu9Psta
+9U5QauWqv0lTniJm0QWUDKn6XdQAJAIV+tASn0kN4L0CfdLWbZoy99B2OAQ1JXxn
+yjELR6jFNTXqJ6F1+7inoirNMlrCHAVFKO6w5fH1mW3iRQu2eFcnXV08/nOdTPzq
+fQIDAQAB
+-----END PUBLIC KEY-----
diff --git a/certificates/testcloud2/event_handler.p12 b/certificates/testcloud2/event_handler.p12
new file mode 100644
index 00000000..42ac3c8e
Binary files /dev/null and b/certificates/testcloud2/event_handler.p12 differ
diff --git a/certificates/testcloud2/gatekeeper.p12 b/certificates/testcloud2/gatekeeper.p12
new file mode 100644
index 00000000..ee984184
Binary files /dev/null and b/certificates/testcloud2/gatekeeper.p12 differ
diff --git a/certificates/testcloud2/gateway.p12 b/certificates/testcloud2/gateway.p12
new file mode 100644
index 00000000..a719f54b
Binary files /dev/null and b/certificates/testcloud2/gateway.p12 differ
diff --git a/certificates/testcloud2/orchestrator.p12 b/certificates/testcloud2/orchestrator.p12
new file mode 100644
index 00000000..5ff6d3bb
Binary files /dev/null and b/certificates/testcloud2/orchestrator.p12 differ
diff --git a/certificates/testcloud2/service_registry_sql.p12 b/certificates/testcloud2/service_registry_sql.p12
new file mode 100644
index 00000000..8ebcbb31
Binary files /dev/null and b/certificates/testcloud2/service_registry_sql.p12 differ
diff --git a/certificates/testcloud2/testcloud2.p12 b/certificates/testcloud2/testcloud2.p12
new file mode 100644
index 00000000..0d97c27e
Binary files /dev/null and b/certificates/testcloud2/testcloud2.p12 differ
diff --git a/certificates/testcloud2/truststore.p12 b/certificates/testcloud2/truststore.p12
new file mode 100644
index 00000000..363b824f
Binary files /dev/null and b/certificates/testcloud2/truststore.p12 differ
diff --git a/core-common/pom.xml b/core-common/pom.xml
new file mode 100644
index 00000000..3fcf840c
--- /dev/null
+++ b/core-common/pom.xml
@@ -0,0 +1,323 @@
+
+
+
+ 4.0.0
+
+
+ eu.arrowhead
+ core
+ ${revision}
+
+
+ arrowhead-core-common
+ jar
+
+
+
+
+ 1.8
+ 1.8
+ 3.8.0
+ 2.10
+ 3.0.2
+ 2.7
+
+
+
+
+
+ com.fasterxml.jackson.jaxrs
+ jackson-jaxrs-json-provider
+
+
+
+ com.fasterxml.jackson.datatype
+ jackson-datatype-jsr310
+
+
+
+ com.zaxxer
+ HikariCP
+
+
+
+ io.swagger.core.v3
+ swagger-jaxrs2
+
+
+
+ javax.servlet
+ javax.servlet-api
+
+
+
+ log4j
+ log4j
+
+
+
+ org.glassfish.jersey.containers
+ jersey-container-grizzly2-http
+
+
+
+ org.glassfish.jersey.containers
+ jersey-container-servlet-core
+
+
+
+ org.glassfish.jersey.ext
+ jersey-bean-validation
+
+
+
+ org.glassfish.jersey.inject
+ jersey-hk2
+
+
+
+ com.sun.xml.bind
+ jaxb-impl
+
+
+
+ com.sun.istack
+ istack-commons-runtime
+
+
+
+ com.sun.xml.txw2
+ txw2
+
+
+
+ org.hibernate
+ hibernate-core
+
+
+
+ org.hibernate
+ hibernate-hikaricp
+
+
+
+ org.hibernate
+ hibernate-java8
+
+
+
+
+
+ mysql
+ mysql-connector-java
+
+
+
+
+
+ org.slf4j
+ slf4j-log4j12
+
+
+
+
+ org.eclipse.milo
+ sdk-client
+ 0.3.0
+
+
+
+ org.eclipse.milo
+ sdk-server
+ 0.3.0
+
+
+
+ com.google.guava
+ guava
+ 27.1-jre
+
+
+
+
+
+
+
+ org.apache.maven.plugins
+ maven-compiler-plugin
+ ${maven.compiler.version}
+
+
+ default-compile
+
+ true
+ true
+
+ ${maven.compiler.target}
+ ${maven.compiler.source}
+
+
+
+
+
+ ${maven.compiler.source}
+ ${maven.compiler.target}
+
+
+
+
+ org.apache.maven.plugins
+ maven-dependency-plugin
+ ${maven.dependency.version}
+
+
+ copy-dependencies
+ package
+
+ copy-dependencies
+
+
+
+ ${basedir}/target/lib/
+
+
+
+
+
+ jdeb
+ org.vafer
+ 1.7
+
+
+ package
+
+ jdeb
+
+
+ true
+ [YYMMddHHmm].${git.commit.id.abbrev}
+ target/${project.artifactId}_${revision}.deb
+ true
+ ${basedir}/src/deb/control
+
+
+
+ file
+ ${project.build.directory}/${project.build.finalName}.jar
+
+ perm
+ /usr/share/arrowhead/lib
+
+
+
+
+ link
+ /usr/share/arrowhead/lib/${project.artifactId}.jar
+ /usr/share/arrowhead/lib/${project.build.finalName}.jar
+
+
+
+ file
+ ${project.basedir}/src/deb/ahconf.sh
+
+ perm
+ /usr/share/arrowhead/conf
+
+
+
+
+ file
+ ${project.basedir}/src/deb/ah_gen_system.sh
+ ah_gen_system
+
+ perm
+ /usr/bin
+ 775
+
+
+
+
+ file
+ ${project.basedir}/src/deb/ah_gen_cloud.sh
+ ah_gen_cloud
+
+ perm
+ /usr/bin
+ 775
+
+
+
+
+ file
+ ${project.basedir}/src/deb/ah_add_neighbor.sh
+ ah_add_neighbor
+
+ perm
+ /usr/bin
+ 775
+
+
+
+
+ file
+ ${project.basedir}/src/deb/ah_gen_quickstart.sh
+ ah_gen_quickstart
+
+ perm
+ /usr/bin
+ 775
+
+
+
+
+ file
+ ${project.basedir}/src/deb/ah_mysql_public.sh
+ ah_mysql_public
+
+ perm
+ /usr/bin
+ 775
+
+
+
+
+ ${project.build.directory}/lib
+ directory
+ *.jar
+
+ perm
+ /usr/share/arrowhead/lib
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/core-common/src/deb/ah_add_neighbor.sh b/core-common/src/deb/ah_add_neighbor.sh
new file mode 100644
index 00000000..74b9cc83
--- /dev/null
+++ b/core-common/src/deb/ah_add_neighbor.sh
@@ -0,0 +1,37 @@
+#!/bin/sh -e
+
+. /usr/share/debconf/confmodule
+. /usr/share/arrowhead/conf/ahconf.sh
+
+if [ "$#" -lt 4 ]; then
+ echo "Syntax: ${0} OPERATOR CLOUD_NAME HOST AUTH_INFO"
+ exit 1
+fi
+
+AH_OPERATOR=${1}
+CLOUD_NAME=${2}
+CLOUD_HOST=${3}
+CLOUD_64PUB=${4}
+
+echo "Registering cloud '${CLOUD_NAME}' in database" >&2
+mysql --defaults-extra-file="${AH_MYSQL_CONF}" -u arrowhead arrowhead <&2
+systemctl restart arrowhead-gateway.service arrowhead-gatekeeper.service
diff --git a/core-common/src/deb/ah_gen_cloud.sh b/core-common/src/deb/ah_gen_cloud.sh
new file mode 100644
index 00000000..ef21606c
--- /dev/null
+++ b/core-common/src/deb/ah_gen_cloud.sh
@@ -0,0 +1,64 @@
+#!/bin/sh -e
+
+. /usr/share/debconf/confmodule
+. /usr/share/arrowhead/conf/ahconf.sh
+
+if [ "$#" -lt 2 ]; then
+ echo "Syntax: ${0} CLOUD_NAME HOST"
+ exit 1
+fi
+
+CLOUD_NAME=${1}
+CLOUD_HOST=${2}
+
+CLOUD_STORE="${AH_CLOUDS_DIR}/${CLOUD_NAME}.p12"
+CLOUD_INIT="${AH_CLOUDS_DIR}/${CLOUD_NAME}.sh"
+
+if [ ! -f "${AH_CONF_DIR}/master.p12" ]; then
+ echo "Keystore for master certificate not found." >&2
+ echo "Generating new clouds only works when existing cloud have been installed in detached mode." >&2
+ exit 1;
+fi
+
+if [ -f "${AH_CLOUDS_DIR}/${CLOUD_NAME}.p12" ]; then
+ echo "'${CLOUD_NAME}' already exist, please remove cloud or use a different name." >&2
+ exit 1;
+fi
+
+echo "Generating certificate for '${CLOUD_NAME}'" >&2
+ah_cert_signed "${AH_CLOUDS_DIR}" ${CLOUD_NAME} "${CLOUD_NAME}.${AH_OPERATOR}.arrowhead.eu" ${AH_CONF_DIR} master
+
+CLOUD_64PUB=$(\
+ sudo keytool -exportcert -rfc -keystore "${CLOUD_STORE}" -storepass ${AH_PASS_CERT} -v -alias "${CLOUD_NAME}" \
+ | openssl x509 -pubkey -noout \
+ | sed '1d;$d' \
+ | tr -d '\n'\
+)
+
+# This restarts gateway and gatekeeper implicitly
+ah_add_neighbor ${AH_OPERATOR} ${CLOUD_NAME} ${CLOUD_HOST} ${CLOUD_64PUB}
+
+echo >&2
+echo "Certificate stored in '${AH_CLOUDS_DIR}'" >&2
+echo "Password for certificate stores: ${AH_PASS_CERT}" >&2
+
+db_get arrowhead-gatekeeper/address; OWN_HOST="$RET"
+
+OWN_64PUB=$(\
+ sudo keytool -exportcert -rfc -keystore "${AH_SYSTEMS_DIR}/gatekeeper/gatekeeper.p12" -storepass ${AH_PASS_CERT} -v -alias "gatekeeper" \
+ | openssl x509 -pubkey -noout \
+ | sed '1d;$d' \
+ | tr -d '\n'\
+)
+
+echo "!/bin/sh" > "${CLOUD_INIT}"
+echo "ah_add_neighbor ${AH_OPERATOR} ${AH_CLOUD_NAME} ${OWN_HOST} ${OWN_64PUB}" > "${CLOUD_INIT}"
+chown :arrowhead ${CLOUD_INIT}
+chmod 640 ${CLOUD_INIT}
+
+echo >&2
+echo "You should call '${CLOUD_INIT}' on your new cloud, after it is installed" >&2
+
+echo >&2
+echo "Helper to copy required files:" >&2
+echo "sudo scp \"${CLOUD_STORE}\" \"${AH_CONF_DIR}/master.crt\" \"${CLOUD_INIT}\" ${CLOUD_HOST}:~" >&2
\ No newline at end of file
diff --git a/core-common/src/deb/ah_gen_quickstart.sh b/core-common/src/deb/ah_gen_quickstart.sh
new file mode 100644
index 00000000..92431bb6
--- /dev/null
+++ b/core-common/src/deb/ah_gen_quickstart.sh
@@ -0,0 +1,27 @@
+#!/bin/sh -e
+
+. /usr/share/debconf/confmodule
+. /usr/share/arrowhead/conf/ahconf.sh
+
+if [ "$#" -lt 1 ]; then
+ echo "Syntax: ${0} HOST"
+ exit 1
+fi
+
+HOST=${1}
+
+ah_gen_system client1 ${HOST} 8080
+echo >&2
+ah_gen_system SecureTemperatureSensor ${HOST} 8461 IndoorTemperature
+echo >&2
+echo "WARNING: No authorization/orchestration entries will be generated by this version of the script" >&2
+
+# TODO Autogenerated ids
+#mysql --defaults-extra-file="${AH_MYSQL_CONF}" -u arrowhead arrowhead <&2
+ exit 1;
+fi
+
+mkdir -p "${SYSTEM_DIR}"
+
+echo "Generating certificate for '${SYSTEM_NAME}'" >&2
+ah_cert_signed_system ${SYSTEM_NAME}
+
+if [ ! -z "${SERVICE}" ]; then
+ ah_cert_export_pub "${AH_SYSTEMS_DIR}/authorization" "authorization" "${SYSTEM_DIR}"
+fi
+
+SYSTEM_64PUB=$(\
+ sudo keytool -exportcert -rfc -keystore "${SYSTEM_STORE}" -storepass ${AH_PASS_CERT} -v -alias "${SYSTEM_NAME}" \
+ | openssl x509 -pubkey -noout \
+ | sed '1d;$d' \
+ | tr -d '\n'\
+)
+
+echo "Registering system '${SYSTEM_NAME}' in database" >&2
+db_cmd="
+ LOCK TABLES arrowhead_system WRITE, table_generator WRITE, arrowhead_service WRITE, arrowhead_service_interfaces WRITE;
+ INSERT INTO arrowhead_system (id, address, authentication_info, port, system_name)
+ SELECT next_val, '${SYSTEM_HOST}', '${SYSTEM_64PUB}', '${PORT}', '${SYSTEM_NAME}' FROM table_generator;
+ UPDATE table_generator SET next_val = next_val + 1;
+"
+
+if [ ! -z "${SERVICE}" ]; then
+ if [ $(mysql --defaults-extra-file="${AH_MYSQL_CONF}" -u arrowhead arrowhead -sse "SELECT EXISTS(SELECT 1 FROM arrowhead_service WHERE service_definition = '${SERVICE}')") != 1 ]; then
+ echo "Registering service '${SERVICE}' in database" >&2
+ db_cmd="${db_cmd}
+ INSERT INTO arrowhead_service (id, service_definition)
+ SELECT next_val, '${SERVICE}' FROM table_generator;
+ INSERT INTO arrowhead_service_interfaces (arrowhead_service_id, interfaces)
+ SELECT next_val, 'JSON' FROM table_generator;
+ UPDATE table_generator SET next_val = next_val + 1;
+ "
+ fi
+fi
+
+db_cmd="${db_cmd} UNLOCK TABLES;"
+
+mysql --defaults-extra-file="${AH_MYSQL_CONF}" -u arrowhead arrowhead -e "${db_cmd}"
+
+if [ -z "${SERVICE}" ]; then
+ echo "Generating consumer-only properties file" >&2
+ echo "" > "${SYSTEM_DIR}/default.conf"
+else
+ echo "Generating full provider properties file" >&2
+ echo "
+######################
+# MANDATORY PARAMETERS
+######################
+
+# Parameters of the offered service which will be registered in the SR
+service_name=${SERVICE}
+# Resource path where the service will be offered (address:port/service_uri)
+service_uri=${SERVICE}
+# Interfaces the service is offered through (comma separated list)
+interfaces=JSON, XML
+# Metadata key-value pairs (key1-value1, key2-value2)
+metadata=unit-celsius
+
+# Provider system name to be registered into the SR
+insecure_system_name=${SYSTEM_NAME}
+secure_system_name=${SYSTEM_NAME}
+fi
+" > "${SYSTEM_DIR}/default.conf"
+fi
+
+echo "
+################################################
+# NON-MANDATORY PARAMETERS (defaults are showed)
+################################################
+
+# Webserver parameters
+address=0.0.0.0
+insecure_port=8460
+secure_port=8461
+
+# Service Registry
+sr_address=0.0.0.0 # ${ARROWHEAD_IPS}
+sr_insecure_port=8442
+sr_secure_port=8443
+
+# Orchestrator
+orch_address=0.0.0.0 # ${ARROWHEAD_IPS}
+orch_insecure_port=8440
+orch_secure_port=8441
+
+#####################################################################
+# MANDATORY PARAMETERS ONLY IN SECURE MODE (invoked w/ -tls argument)
+#####################################################################
+
+# Certificate related paths and passwords
+keystore=${SYSTEM_STORE}
+keystorepass=${AH_PASS_CERT}
+keypass=${AH_PASS_CERT}
+truststore=${SYSTEM_STORE}
+truststorepass=${AH_PASS_CERT}
+authorization_cert=${SYSTEM_DIR}/authorization.pub
+" >> "${SYSTEM_DIR}/default.conf"
+
+chown root:arrowhead "${SYSTEM_DIR}/default.conf"
+chmod 640 "${SYSTEM_DIR}/default.conf"
+
+echo >&2
+echo "System files stored in '${SYSTEM_DIR}'" >&2
+echo "Please verify that 'default.conf' is correct" >&2
diff --git a/core-common/src/deb/ah_mysql_public.sh b/core-common/src/deb/ah_mysql_public.sh
new file mode 100644
index 00000000..41438bab
--- /dev/null
+++ b/core-common/src/deb/ah_mysql_public.sh
@@ -0,0 +1,38 @@
+#!/bin/bash -e
+
+. /usr/share/debconf/confmodule
+. /usr/share/arrowhead/conf/ahconf.sh
+
+do_mysql_conf() {
+ BAK="$(dirname "$1")/$(basename "$1").ah_bak"
+
+ if [[ -e "${BAK}" ]]; then
+ echo "'${BAK}' exists, please remove it and try again" >&2
+ exit 1
+ fi
+
+ cp -v "$1" "${BAK}"
+
+ echo "Setting bind-address = 0.0.0.0 in '$1'" >&2
+ sed -i 's/^\(bind-address[ \t]*=[ \t]*\).*$/\10.0.0.0/' "$1"
+}
+
+MYSQL_CONF_UBUNTU="/etc/mysql/mysql.conf.d/mysqld.cnf"
+MARIADB_CONF_PI="/etc/mysql/mariadb.conf.d/50-server.cnf"
+
+if [[ -r "${MYSQL_CONF_UBUNTU}" ]]; then
+ do_mysql_conf "${MYSQL_CONF_UBUNTU}"
+elif [[ -r "${MARIADB_CONF_PI}" ]]; then
+ do_mysql_conf "${MARIADB_CONF_PI}"
+fi
+
+if [ $(mysql --defaults-extra-file="${AH_MYSQL_CONF}" -u arrowhead -sse "SELECT EXISTS(SELECT 1 FROM mysql.user WHERE user = 'arrowhead' AND host = '%')") != 1 ]; then
+ mysql -e "CREATE USER arrowhead@'%' IDENTIFIED BY '${AH_PASS_DB}';"
+ mysql -e "GRANT ALL PRIVILEGES ON arrowhead.* TO arrowhead@'%';"
+ mysql -e "FLUSH PRIVILEGES;"
+fi
+systemctl restart mysql
+
+echo >&2
+echo "Use MySQL user 'arrowhead' with password '${AH_PASS_DB}'" >&2
+echo >&2
diff --git a/core-common/src/deb/ahconf.sh b/core-common/src/deb/ahconf.sh
new file mode 100644
index 00000000..5133baea
--- /dev/null
+++ b/core-common/src/deb/ahconf.sh
@@ -0,0 +1,287 @@
+#!/bin/sh
+
+AH_CONF_DIR="/etc/arrowhead"
+AH_CLOUDS_DIR="${AH_CONF_DIR}/clouds"
+AH_SYSTEMS_DIR="${AH_CONF_DIR}/systems"
+AH_MYSQL_CONF="${AH_CONF_DIR}/mysql.cnf"
+
+db_get arrowhead-core-common/mysql_password; AH_PASS_DB=$RET
+db_get arrowhead-core-common/cert_password; AH_PASS_CERT=$RET
+db_get arrowhead-core-common/cloudname; AH_CLOUD_NAME=$RET
+db_get arrowhead-core-common/operator; AH_OPERATOR=$RET
+db_get arrowhead-core-common/company; AH_COMPANY=$RET
+db_get arrowhead-core-common/country; AH_COUNTRY=$RET
+
+ah_cert () {
+ dst_path=${1}
+ dst_name=${2}
+ cn=${3}
+
+ file="${dst_path}/${dst_name}.p12"
+
+ # The command has been renamed in newer versions of keytool
+ gen_cmd="-genkeypair"
+ keytool ${gen_cmd} --help >/dev/null 2>&1 || gen_cmd='-genkey'
+
+ if [ ! -f "${file}" ]; then
+ keytool ${gen_cmd} \
+ -alias ${dst_name} \
+ -keyalg RSA \
+ -keysize 2048 \
+ -dname "CN=${cn}, OU=${AH_OPERATOR}, O=${AH_COMPANY}, C=${AH_COUNTRY}" \
+ -validity 3650 \
+ -keypass ${AH_PASS_CERT} \
+ -keystore ${file} \
+ -storepass ${AH_PASS_CERT} \
+ -storetype PKCS12 \
+ -ext BasicConstraints:"Subject is a CA\nPath Length Constraint: None"
+
+ chown :arrowhead ${file}
+ chmod 640 ${file}
+ fi
+}
+
+ah_cert_export () {
+ src_path=${1}
+ dst_name=${2}
+ dst_path=${3}
+
+ src_file="${src_path}/${dst_name}.p12"
+ dst_file="${dst_path}/${dst_name}.crt"
+
+ if [ ! -f "${dst_file}" ]; then
+ keytool -exportcert \
+ -rfc \
+ -alias ${dst_name} \
+ -storepass ${AH_PASS_CERT} \
+ -keystore ${src_file} \
+ | openssl x509 \
+ -out ${dst_file}
+
+ chown :arrowhead ${dst_file}
+ chmod 640 ${dst_file}
+ fi
+}
+
+ah_cert_export_pub () {
+ src_path=${1}
+ dst_name=${2}
+ dst_path=${3}
+
+ src_file="${src_path}/${dst_name}.p12"
+ dst_file="${dst_path}/${dst_name}.pub"
+
+ if [ ! -f "${dst_file}" ]; then
+ keytool -exportcert \
+ -rfc \
+ -alias ${dst_name} \
+ -storepass ${AH_PASS_CERT} \
+ -keystore ${src_file} \
+ | openssl x509 \
+ -out ${dst_file} \
+ -noout \
+ -pubkey
+
+ chown :arrowhead ${dst_file}
+ chmod 640 ${dst_file}
+ fi
+}
+
+ah_cert_import () {
+ src_path=${1}
+ src_name=${2}
+ dst_path=${3}
+ dst_name=${4}
+
+ src_file="${src_path}/${src_name}.crt"
+ dst_file="${dst_path}/${dst_name}.p12"
+
+ keytool -import \
+ -trustcacerts \
+ -file ${src_file} \
+ -alias ${src_name} \
+ -keystore ${dst_file} \
+ -keypass ${AH_PASS_CERT} \
+ -storepass ${AH_PASS_CERT} \
+ -storetype PKCS12 \
+ -noprompt
+}
+
+ah_cert_signed () {
+ dst_path=${1}
+ dst_name=${2}
+ cn=${3}
+ src_path=${4}
+ src_name=${5}
+
+ src_file="${src_path}/${src_name}.p12"
+ dst_file="${dst_path}/${dst_name}.p12"
+
+ if [ ! -f "${dst_file}" ]; then
+ ah_cert ${dst_path} ${dst_name} ${cn}
+
+ keytool -export \
+ -alias ${src_name} \
+ -storepass ${AH_PASS_CERT} \
+ -keystore ${src_file} \
+ | keytool -import \
+ -trustcacerts \
+ -alias ${src_name} \
+ -keystore ${dst_file} \
+ -keypass ${AH_PASS_CERT} \
+ -storepass ${AH_PASS_CERT} \
+ -storetype PKCS12 \
+ -noprompt
+
+ keytool -certreq \
+ -alias ${dst_name} \
+ -keypass ${AH_PASS_CERT} \
+ -keystore ${dst_file} \
+ -storepass ${AH_PASS_CERT} \
+ | keytool -gencert \
+ -alias ${src_name} \
+ -keypass ${AH_PASS_CERT} \
+ -keystore ${src_file} \
+ -storepass ${AH_PASS_CERT} \
+ | keytool -importcert \
+ -alias ${dst_name} \
+ -keypass ${AH_PASS_CERT} \
+ -keystore ${dst_file} \
+ -storepass ${AH_PASS_CERT} \
+ -noprompt
+ fi
+}
+
+ah_cert_signed_system () {
+ name=${1}
+
+ path="${AH_SYSTEMS_DIR}/${name}"
+ file="${path}/${name}.p12"
+
+ if [ ! -f "${file}" ]; then
+ ah_cert_signed \
+ "${path}" \
+ ${name} \
+ "${name}.${AH_CLOUD_NAME}.${AH_OPERATOR}.arrowhead.eu" \
+ ${AH_CLOUDS_DIR} \
+ ${AH_CLOUD_NAME}
+
+ ah_cert_import "${AH_CONF_DIR}" "master" "${path}" ${name}
+ fi
+}
+
+ah_cert_trust () {
+ dst_path=${1}
+ src_path=${2}
+ src_name=${3}
+
+ src_file="${src_path}/${src_name}.p12"
+ dst_file="${dst_path}/truststore.p12"
+
+ if [ ! -f "${dst_file}" ]; then
+ keytool -export \
+ -alias ${src_name} \
+ -storepass ${AH_PASS_CERT} \
+ -keystore ${src_file} \
+ | keytool -import \
+ -trustcacerts \
+ -alias ${src_name} \
+ -keystore ${dst_file} \
+ -keypass ${AH_PASS_CERT} \
+ -storepass ${AH_PASS_CERT} \
+ -storetype PKCS12 \
+ -noprompt
+
+ chown :arrowhead ${dst_file}
+ chmod 640 ${dst_file}
+ fi
+}
+
+ah_db_user () {
+ if [ ! -f "${AH_MYSQL_CONF}" ]; then
+ touch "${AH_MYSQL_CONF}"
+ chmod 0600 "${AH_MYSQL_CONF}"
+ cat >"${AH_MYSQL_CONF}" </dev/null 2>/dev/null; then
+ if mysql -u root -e "SHOW DATABASES" >/dev/null 2>/dev/null; then
+ mysql -u root <"${OPT_FILE}" <${file}
+# Define the root logger with appender file
+log4j.rootLogger=INFO, DB, FILE
+
+# Database related config
+# Define the DB appender
+log4j.appender.DB=org.apache.log4j.jdbc.JDBCAppender
+# Set Database URL
+log4j.appender.DB.URL=jdbc:mysql://127.0.0.1:3306/arrowhead
+# Set database user name and password
+log4j.appender.DB.user=arrowhead
+log4j.appender.DB.password=${AH_PASS_DB}
+# Set the SQL statement to be executed.
+log4j.appender.DB.sql=INSERT INTO logs(id, date, origin, level, message) VALUES(DEFAULT,'%d{yyyy-MM-dd HH:mm:ss}','%C','%p','%m')
+# Define the layout for file appender
+log4j.appender.DB.layout=org.apache.log4j.PatternLayout
+# Disable Hibernate verbose logging
+log4j.logger.org.hibernate=fatal
+
+# File related config
+# Define the file appender
+log4j.appender.FILE=org.apache.log4j.FileAppender
+# Set the name of the file
+log4j.appender.FILE.File=/var/log/arrowhead/${system_name}.log
+# Set the immediate flush to true (default)
+log4j.appender.FILE.ImmediateFlush=true
+# Set the threshold to debug mode
+log4j.appender.FILE.Threshold=debug
+# Set the append to false, overwrite
+log4j.appender.FILE.Append=false
+# Define the layout for file appender
+log4j.appender.FILE.layout=org.apache.log4j.PatternLayout
+log4j.appender.FILE.layout.conversionPattern=%d{yyyy-MM-dd HH:mm:ss}, %C, %p, %m%n
+EOF
+ chown root:arrowhead ${file}
+ chmod 640 ${file}
+ fi
+}
diff --git a/core-common/src/deb/control/config b/core-common/src/deb/control/config
new file mode 100644
index 00000000..fe6a85ac
--- /dev/null
+++ b/core-common/src/deb/control/config
@@ -0,0 +1,161 @@
+#!/bin/sh -e
+
+. /usr/share/debconf/confmodule
+. /usr/share/arrowhead/conf/ahconf.sh
+
+if logname 2>/dev/null; then
+ USER=$(logname)
+ HOME=$(eval echo "~$USER")
+else
+ USER="root"
+ HOME="/root"
+fi
+
+# Get installation type
+db_input high arrowhead-core-common/install_type || true
+db_go || true
+db_get arrowhead-core-common/install_type; INSTALL_TYPE="$RET"
+
+# User dialog
+case ${INSTALL_TYPE} in
+ "Authorized" )
+ if [ ! -f ${AH_CLOUDS_DIR}/${AH_CLOUD_NAME}.p12 ]; then
+ # Set default path for cloud certificate
+ db_get arrowhead-core-common/cloud_cert;
+ if [ -z "$RET" ]; then
+ db_set arrowhead-core-common/cloud_cert "${HOME}/cloud.p12"
+ fi
+
+ # Set default path for master certificate
+ db_get arrowhead-core-common/master_cert; master_cert=$RET
+ if [ -z "$RET" ]; then
+ db_set arrowhead-core-common/master_cert "${HOME}/master.crt"
+ fi
+
+ # User dialogs
+ db_input critical arrowhead-core-common/cloud_cert || true
+ db_input critical arrowhead-core-common/cloud_alias || true
+ db_input critical arrowhead-core-common/cloud_password || true
+ db_input critical arrowhead-core-common/master_cert || true
+ fi
+ ;;
+
+ "Detached" )
+ # User dialogs
+ db_input high arrowhead-core-common/cloudname || true
+ db_input high arrowhead-core-common/operator || true
+ db_input high arrowhead-core-common/company || true
+ db_input high arrowhead-core-common/country || true
+ ;;
+
+ * )
+ echo "Unexpected install type"
+ exit 1
+ ;;
+esac
+
+# Get certificate password
+if [ -z "${AH_PASS_CERT}" ]; then
+ db_input high arrowhead-core-common/cert_password || true
+fi
+
+# Get database password
+if [ -z "${AH_PASS_DB}" ]; then
+ db_input high arrowhead-core-common/mysql_password || true
+fi
+
+# Run dialogs
+db_go || true
+
+# Create user
+adduser --system --no-create-home --group arrowhead
+
+# Create directories
+mkdir -p ${AH_CLOUDS_DIR}/
+if [ ! -d /var/log/arrowhead ]; then
+ mkdir -p /var/log/arrowhead
+ chown arrowhead:adm /var/log/arrowhead
+ chmod 750 /var/log/arrowhead
+fi
+
+# Reload global variables
+. /usr/share/arrowhead/conf/ahconf.sh
+
+# Generate certificate password (if required)
+if [ -z "${AH_PASS_CERT}" ]; then
+ PASS="$(openssl rand -base64 12)"
+ db_set arrowhead-core-common/cert_password ${PASS}
+fi
+
+# Generate database password (if required)
+if [ -z "${AH_PASS_DB}" ]; then
+ PASS="$(openssl rand -base64 12)"
+ db_set arrowhead-core-common/mysql_password ${PASS}
+fi
+
+case ${INSTALL_TYPE} in
+ "Authorized" )
+ # Get additional variables
+ db_get arrowhead-core-common/cloud_cert; cloud_cert=$RET
+ db_get arrowhead-core-common/cloud_alias; cloud_alias=$RET
+ db_get arrowhead-core-common/cloud_password; cloud_password=$RET
+ db_get arrowhead-core-common/master_cert; master_cert=$RET
+
+ # Extract data from CN field
+ subject=$(keytool -list -keystore "${cloud_cert}" -storepass "${cloud_password}" -v -alias "${cloud_alias}" | grep "Owner:" | head -n1 | sed 's|Owner: ||')
+ cloud_name=$(echo "${subject}" | sed 's|^.*CN=\([^.]*\)\.\([^.]*\)\.\([^.]*\)\.\([^,]*\),.*$|\1|')
+ operator=$(echo "${subject}" | sed 's|^.*CN=\([^.]*\)\.\([^.]*\)\.\([^.]*\)\.\([^,]*\),.*$|\2|')
+ company=$(echo "${subject}" | sed 's|^.*CN=\([^.]*\)\.\([^.]*\)\.\([^.]*\)\.\([^,]*\),.*$|\3|')
+ country=$(echo "${subject}" | sed 's|^.*CN=\([^.]*\)\.\([^.]*\)\.\([^.]*\)\.\([^,]*\),.*$|\4|')
+
+ # Store data
+ db_set arrowhead-core-common/cloudname ${cloud_name}
+ db_set arrowhead-core-common/operator ${operator}
+ db_set arrowhead-core-common/company ${company}
+ db_set arrowhead-core-common/country ${country}
+ ;;
+esac
+
+# Reload global variables
+. /usr/share/arrowhead/conf/ahconf.sh
+
+# Do actions based on install type
+case ${INSTALL_TYPE} in
+ "Authorized" )
+ if [ ! -f ${AH_CLOUDS_DIR}/${AH_CLOUD_NAME}.p12 ]; then
+ # Store cloud certificate
+ keytool -importkeystore \
+ -srckeypass ${cloud_password} \
+ -destkeypass ${AH_PASS_CERT} \
+ -srcstorepass ${cloud_password} \
+ -deststorepass ${AH_PASS_CERT} \
+ -srcalias ${cloud_alias} \
+ -destalias ${AH_CLOUD_NAME} \
+ -srckeystore "${cloud_cert}" \
+ -destkeystore "${AH_CLOUDS_DIR}/${AH_CLOUD_NAME}.p12" \
+ -deststoretype PKCS12
+ chown :arrowhead "${AH_CLOUDS_DIR}/${AH_CLOUD_NAME}.p12"
+ chmod 640 "${AH_CLOUDS_DIR}/${AH_CLOUD_NAME}.p12"
+
+ # Store master certificate
+ cp "${master_cert}" "${AH_CONF_DIR}/master.crt"
+ chown :arrowhead "${AH_CONF_DIR}/master.crt"
+ chmod 640 "${AH_CONF_DIR}/master.crt"
+ fi
+ ;;
+
+ "Detached" )
+ # Generate master and cloud certificates
+ ah_cert ${AH_CONF_DIR} master "arrowhead.eu"
+ ah_cert_export "${AH_CONF_DIR}" master "${AH_CONF_DIR}"
+ ah_cert_signed ${AH_CLOUDS_DIR} ${AH_CLOUD_NAME} "${AH_CLOUD_NAME}.${AH_OPERATOR}.arrowhead.eu" ${AH_CONF_DIR} master
+ ;;
+
+ * )
+ echo "Unexpected install type"
+ exit 1
+ ;;
+esac
+
+# Trust the cloud certificate
+ah_cert_trust ${AH_CONF_DIR} ${AH_CLOUDS_DIR} ${AH_CLOUD_NAME}
diff --git a/core-common/src/deb/control/control b/core-common/src/deb/control/control
new file mode 100644
index 00000000..76a9ee34
--- /dev/null
+++ b/core-common/src/deb/control/control
@@ -0,0 +1,10 @@
+Package: [[name]]
+Version: [[version]]
+Section: contrib/java
+Priority: optional
+Architecture: all
+Maintainer: Thomas Pedersen
+Homepage: http://www.arrowhead.eu
+Description: Arrowhead Common
+Distribution: development
+Depends: java-runtime-headless, openssl, debconf
diff --git a/core-common/src/deb/control/postinst b/core-common/src/deb/control/postinst
new file mode 100644
index 00000000..b52d9c7a
--- /dev/null
+++ b/core-common/src/deb/control/postinst
@@ -0,0 +1,41 @@
+#!/bin/sh
+# postinst script for arrowhead-core-common
+#
+# see: dh_installdeb(1)
+
+set -e
+
+. /usr/share/debconf/confmodule
+
+# summary of how this script can be called:
+# * `configure'
+# * `abort-upgrade'
+# * `abort-remove' `in-favour'
+#
+# * `abort-remove'
+# * `abort-deconfigure' `in-favour'
+# `removing'
+#
+# for details, see https://www.debian.org/doc/debian-policy/ or
+# the debian-policy package
+
+
+case "$1" in
+ configure)
+ ;;
+
+ abort-upgrade|abort-remove|abort-deconfigure)
+ ;;
+
+ *)
+ echo "postinst called with unknown argument \`$1'" >&2
+ exit 1
+ ;;
+esac
+
+# dh_installdeb will replace this with shell code automatically
+# generated by other debhelper scripts.
+
+#DEBHELPER#
+
+exit 0
diff --git a/core-common/src/deb/control/postrm b/core-common/src/deb/control/postrm
new file mode 100644
index 00000000..a3a10bc3
--- /dev/null
+++ b/core-common/src/deb/control/postrm
@@ -0,0 +1,91 @@
+#!/bin/sh
+# postrm script for arrowhead-core-common
+#
+# see: dh_installdeb(1)
+
+set -e
+
+. /usr/share/debconf/confmodule
+
+# summary of how this script can be called:
+# * `remove'
+# * `purge'
+# * `upgrade'
+# * `failed-upgrade'
+# * `abort-install'
+# * `abort-install'
+# * `abort-upgrade'
+# * `disappear'
+#
+# for details, see https://www.debian.org/doc/debian-policy/ or
+# the debian-policy package
+
+
+case "$1" in
+ purge)
+ db_get arrowhead-core-common/cloudname; AH_CLOUD_NAME=$RET
+ AH_CONF_DIR="/etc/arrowhead"
+ AH_CLOUDS_DIR="${AH_CONF_DIR}/clouds"
+ AH_SYSTEMS_DIR="${AH_CONF_DIR}/systems"
+ AH_MYSQL_CONF="${AH_CONF_DIR}/mysql.cnf"
+
+ if command -v mysql 2>&1 >/dev/null; then
+ if mysql -u root -e "SHOW DATABASES" >/dev/null 2>/dev/null; then
+ mysql -u root -p <"${OPT_FILE}" </dev/null || true
+ rmdir ${AH_SYSTEMS_DIR} 2>/dev/null || true
+ rmdir ${AH_CONF_DIR} 2>/dev/null || true
+ rmdir /var/log/arrowhead 2>/dev/null || true
+
+ deluser arrowhead || true
+ db_purge
+
+ if [ -d ${AH_CONF_DIR} ]; then
+ echo "Directory '${AH_CONF_DIR}' is not empty, please remove manually" >&2
+ fi
+ ;;
+ remove|upgrade|failed-upgrade|abort-install|abort-upgrade|disappear)
+ ;;
+
+ *)
+ echo "postrm called with unknown argument \`$1'" >&2
+ exit 1
+ ;;
+esac
+
+# dh_installdeb will replace this with shell code automatically
+# generated by other debhelper scripts.
+
+#DEBHELPER#
+
+exit 0
diff --git a/core-common/src/deb/control/preinst b/core-common/src/deb/control/preinst
new file mode 100644
index 00000000..c3054905
--- /dev/null
+++ b/core-common/src/deb/control/preinst
@@ -0,0 +1,35 @@
+#!/bin/sh
+# preinst script for arrowhead-core-common
+#
+# see: dh_installdeb(1)
+
+set -e
+
+# summary of how this script can be called:
+# * `install'
+# * `install'
+# * `upgrade'
+# * `abort-upgrade'
+# for details, see https://www.debian.org/doc/debian-policy/ or
+# the debian-policy package
+
+
+case "$1" in
+ install|upgrade)
+ ;;
+
+ abort-upgrade)
+ ;;
+
+ *)
+ echo "preinst called with unknown argument \`$1'" >&2
+ exit 1
+ ;;
+esac
+
+# dh_installdeb will replace this with shell code automatically
+# generated by other debhelper scripts.
+
+#DEBHELPER#
+
+exit 0
diff --git a/core-common/src/deb/control/prerm b/core-common/src/deb/control/prerm
new file mode 100644
index 00000000..56d6beab
--- /dev/null
+++ b/core-common/src/deb/control/prerm
@@ -0,0 +1,38 @@
+#!/bin/sh
+# prerm script for arrowhead-core-common
+#
+# see: dh_installdeb(1)
+
+set -e
+
+# summary of how this script can be called:
+# * `remove'
+# * `upgrade'
+# * `failed-upgrade'
+# * `remove' `in-favour'
+# * `deconfigure' `in-favour'
+# `removing'
+#
+# for details, see https://www.debian.org/doc/debian-policy/ or
+# the debian-policy package
+
+
+case "$1" in
+ remove|upgrade|deconfigure)
+ ;;
+
+ failed-upgrade)
+ ;;
+
+ *)
+ echo "prerm called with unknown argument \`$1'" >&2
+ exit 1
+ ;;
+esac
+
+# dh_installdeb will replace this with shell code automatically
+# generated by other debhelper scripts.
+
+#DEBHELPER#
+
+exit 0
diff --git a/core-common/src/deb/control/templates b/core-common/src/deb/control/templates
new file mode 100644
index 00000000..63da5ac4
--- /dev/null
+++ b/core-common/src/deb/control/templates
@@ -0,0 +1,63 @@
+Template: arrowhead-core-common/install_type
+Type: select
+Choices: Detached, Authorized
+Description: Do you want to perform (1) a detached, standalone install or (2) authorized install with externally issued cloud cert?
+
+Template: arrowhead-core-common/cloud_cert
+Type: string
+Default:
+Description: Enter the path for the externally issued cloud cert
+
+Template: arrowhead-core-common/master_cert
+Type: string
+Default:
+Description: Enter the path for the externally issued cloud cert
+
+Template: arrowhead-core-common/cloud_password
+Type: password
+Default:
+Description: Enter the password for the externally issued cloud cert
+
+Template: arrowhead-core-common/cloud_alias
+Type: string
+Default:
+Description: Enter the alias for the externally issued cloud cert
+
+Template: arrowhead-core-common/cloudname
+Type: string
+Default: testcloud
+Description: The name of the Arrowhead local cloud
+
+Template: arrowhead-core-common/operator
+Type: string
+Default: user
+Description: The name of the operator of the local cloud
+
+Template: arrowhead-core-common/company
+Type: string
+Default: arrowhead
+Description: The name of your company
+
+Template: arrowhead-core-common/country
+Type: string
+Default: eu
+Description: The name of your country
+
+Template: arrowhead-core-common/mysql_password_root
+Type: password
+Default:
+Description: Password for the MySQL root user. Note that password is stored by debconf in '/var/cache/debconf/passwords.dat'.
+
+Template: arrowhead-core-common/mysql_password
+Type: password
+Default:
+Description: Password for the generated MySQL arrowhead user (leave empty for random). Note that password is stored by debconf in '/var/cache/debconf/passwords.dat'.
+
+Template: arrowhead-core-common/cert_password
+Type: password
+Default:
+Description: Password for the certificates and stores (leave empty for random). Note that password is stored by debconf in '/var/cache/debconf/passwords.dat'.
+
+Template: arrowhead-core-common/reconf_warning
+Type: note
+Description: You have to manually reconfigure all Arrowhead systems when changing passwords
diff --git a/core-common/src/main/java/eu/arrowhead/common/ArrowheadMain.java b/core-common/src/main/java/eu/arrowhead/common/ArrowheadMain.java
new file mode 100644
index 00000000..7df6b351
--- /dev/null
+++ b/core-common/src/main/java/eu/arrowhead/common/ArrowheadMain.java
@@ -0,0 +1,329 @@
+/*
+ * This work is part of the Productive 4.0 innovation project, which receives grants from the
+ * European Commissions H2020 research and innovation programme, ECSEL Joint Undertaking
+ * (project no. 737459), the free state of Saxony, the German Federal Ministry of Education and
+ * national funding authorities from involved countries.
+ */
+
+package eu.arrowhead.common;
+
+import java.io.BufferedReader;
+import java.io.IOException;
+import java.io.InputStreamReader;
+import java.net.URI;
+import java.security.KeyStore;
+import java.security.cert.X509Certificate;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Base64;
+import java.util.Collections;
+import java.util.List;
+import java.util.Map;
+import java.util.ServiceConfigurationError;
+import java.util.Set;
+import java.util.concurrent.CompletableFuture;
+
+import javax.net.ssl.SSLContext;
+import javax.ws.rs.ProcessingException;
+import javax.ws.rs.core.UriBuilder;
+
+import org.apache.log4j.Logger;
+import org.apache.log4j.PropertyConfigurator;
+import org.eclipse.milo.opcua.sdk.server.UaNodeManager;
+import org.eclipse.milo.opcua.sdk.server.nodes.UaNodeContext;
+import org.glassfish.grizzly.http.server.CLStaticHttpHandler;
+import org.glassfish.grizzly.http.server.HttpHandler;
+import org.glassfish.grizzly.http.server.HttpServer;
+import org.glassfish.grizzly.ssl.SSLContextConfigurator;
+import org.glassfish.grizzly.ssl.SSLContextConfigurator.GenericStoreException;
+import org.glassfish.grizzly.ssl.SSLEngineConfigurator;
+import org.glassfish.jersey.grizzly2.httpserver.GrizzlyHttpServerFactory;
+import org.glassfish.jersey.server.ResourceConfig;
+
+import eu.arrowhead.common.database.ArrowheadService;
+import eu.arrowhead.common.database.ArrowheadSystem;
+import eu.arrowhead.common.database.ServiceRegistryEntry;
+import eu.arrowhead.common.exception.ArrowheadException;
+import eu.arrowhead.common.exception.AuthException;
+import eu.arrowhead.common.exception.ExceptionType;
+import eu.arrowhead.common.misc.CoreSystem;
+import eu.arrowhead.common.misc.CoreSystemService;
+import eu.arrowhead.common.misc.SecurityUtils;
+import eu.arrowhead.common.misc.TypeSafeProperties;
+import eu.arrowhead.common.opcua.ArrowheadOpcUaServer;
+//import eu.arrowhead.common.opcua.ArrowheadOpcUaServer;
+
+public abstract class ArrowheadMain {
+
+ public static final List dbFields = Collections
+ .unmodifiableList(Arrays.asList("db_user", "db_password", "db_address"));
+ public static final List certFields = Collections
+ .unmodifiableList(Arrays.asList("keystore", "keystorepass", "keypass", "truststore", "truststorepass"));
+ public static final Map secureServerMetadata = Collections.singletonMap("security", "certificate");
+
+ protected String srBaseUri;
+ protected final TypeSafeProperties props = Utility.getProp();
+
+ private boolean daemon = false;
+ private CoreSystem coreSystem;
+ private HttpServer server;
+ protected ArrowheadOpcUaServer arrowheaduaserver;
+ private String baseUri;
+ private String base64PublicKey;
+ private int registeringTries = 1;
+
+ private static final Logger log = Logger.getLogger(ArrowheadMain.class.getName());
+
+ {
+ DatabaseManager.init();
+ PropertyConfigurator.configure(props);
+ }
+
+ protected void init(CoreSystem coreSystem, String[] args, Set> classes, String[] packages) {
+ System.out.println("Working directory: " + System.getProperty("user.dir"));
+ packages = addSwaggerToPackages(packages);
+ this.coreSystem = coreSystem;
+
+ boolean isSecure = false;
+ // Read in command line arguments
+ for (String arg : args) {
+ switch (arg) {
+ case "-daemon":
+ daemon = true;
+ System.out.println("Starting server as daemon!");
+ break;
+ case "-d":
+ System.setProperty("debug_mode", "true");
+ System.out.println("Starting server in debug mode!");
+ break;
+ case "-tls":
+ System.setProperty("is_secure", "true");
+ isSecure = true;
+ break;
+ }
+ }
+
+ // Get the URL where the web-server will bind to
+ String address = props.getProperty("address", "0.0.0.0");
+ int port = isSecure ? props.getIntProperty("secure_port", coreSystem.getSecurePort())
+ : props.getIntProperty("insecure_port", coreSystem.getInsecurePort());
+ baseUri = Utility.getUri(address, port, null, isSecure, true);
+
+ // Start the web-server
+ if (isSecure) {
+ List allMandatoryProperties = new ArrayList<>(coreSystem.getAlwaysMandatoryFields());
+ allMandatoryProperties.addAll(coreSystem.getSecureMandatoryFields());
+ Utility.checkProperties(props.stringPropertyNames(), allMandatoryProperties);
+ startSecureServer(classes, packages);
+ } else {
+ Utility.checkProperties(props.stringPropertyNames(), coreSystem.getAlwaysMandatoryFields());
+ startServer(classes, packages);
+ }
+
+ // Register the core system services to the Service Registry
+ if (!coreSystem.equals(CoreSystem.SERVICE_REGISTRY_DNS)
+ && !coreSystem.equals(CoreSystem.SERVICE_REGISTRY_SQL)) {
+ String srAddress = props.getProperty("sr_address", "0.0.0.0");
+ int srPort = isSecure
+ ? props.getIntProperty("sr_secure_port", CoreSystem.SERVICE_REGISTRY_SQL.getSecurePort())
+ : props.getIntProperty("sr_insecure_port", CoreSystem.SERVICE_REGISTRY_SQL.getInsecurePort());
+ srBaseUri = Utility.getUri(srAddress, srPort, "serviceregistry", isSecure, true);
+ Utility.setServiceRegistryUri(srBaseUri);
+ useSRService(true);
+ }
+ }
+
+ protected void listenForInput() {
+ log.info(coreSystem + " startup completed.");
+ if (daemon) {
+ System.out.println("In daemon mode, process will terminate for TERM signal...");
+ Runtime.getRuntime().addShutdownHook(new Thread(() -> {
+ System.out.println("Received TERM signal, shutting down...");
+ shutdown();
+ }));
+ } else {
+ System.out.println("Type \"stop\" to shutdown " + coreSystem.name() + " Server...");
+ BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
+ String input = "";
+ try {
+ while (!input.equals("stop")) {
+ input = br.readLine();
+ }
+ br.close();
+ } catch (IOException e) {
+ e.printStackTrace();
+ }
+ shutdown();
+ }
+ }
+
+ private void startServer(Set> classes, String[] packages) {
+ final ResourceConfig config = new ResourceConfig();
+ config.registerClasses(classes);
+ config.packages(packages);
+
+ URI uri = UriBuilder.fromUri(baseUri).build();
+ try {
+ server = GrizzlyHttpServerFactory.createHttpServer(uri, config, false);
+ configureServer(server);
+ server.start();
+ log.info("Started server at: " + baseUri);
+ System.out.println("Started insecure server at: " + baseUri);
+ } catch (IOException | ProcessingException e) {
+ throw new ServiceConfigurationError(
+ "Make sure you gave a valid address in the config file! (Assignable to this JVM and not in use already)",
+ e);
+ }
+ }
+
+ protected void startSecureServer(Set> classes, String[] packages) {
+ final ResourceConfig config = new ResourceConfig();
+ config.registerClasses(classes);
+ config.packages(packages);
+
+ String keystorePath = props.getProperty("keystore");
+ String keystorePass = props.getProperty("keystorepass");
+ String keyPass = props.getProperty("keypass");
+ String truststorePath = props.getProperty("truststore");
+ String truststorePass = props.getProperty("truststorepass");
+
+ SSLContextConfigurator sslCon = new SSLContextConfigurator();
+ sslCon.setKeyStoreFile(keystorePath);
+ sslCon.setKeyStorePass(keystorePass);
+ sslCon.setKeyPass(keyPass);
+ sslCon.setTrustStoreFile(truststorePath);
+ sslCon.setTrustStorePass(truststorePass);
+ SSLContext sslContext;
+ try {
+ sslContext = sslCon.createSSLContext(true);
+ } catch (GenericStoreException e) {
+ log.fatal("SSL Context is not valid, check the certificate or the config files!");
+ throw new AuthException("SSL Context is not valid, check the certificate or the config files!", e);
+ }
+ Utility.setSSLContext(sslContext);
+
+ KeyStore keyStore = SecurityUtils.loadKeyStore(keystorePath, keystorePass);
+ X509Certificate serverCert = SecurityUtils.getFirstCertFromKeyStore(keyStore);
+ base64PublicKey = Base64.getEncoder().encodeToString(serverCert.getPublicKey().getEncoded());
+ System.out.println("Server PublicKey Base64: " + base64PublicKey);
+ String serverCN = SecurityUtils.getCertCNFromSubject(serverCert.getSubjectDN().getName());
+ if (!SecurityUtils.isKeyStoreCNArrowheadValid(serverCN)) {
+ log.fatal("Server CN is not compliant with the Arrowhead cert structure");
+ throw new AuthException("Server CN ( " + serverCN
+ + ") is not compliant with the Arrowhead cert structure, since it does not have 5 "
+ + "parts, or does not end with" + " \"arrowhead.eu\"");
+ }
+ log.info("Certificate of the secure server: " + serverCN);
+ config.property("server_common_name", serverCN);
+
+ URI uri = UriBuilder.fromUri(baseUri).build();
+ try {
+ server = GrizzlyHttpServerFactory.createHttpServer(uri, config, true,
+ new SSLEngineConfigurator(sslCon).setClientMode(false).setNeedClientAuth(true), false);
+ configureServer(server);
+ server.start();
+ log.info("Started server at: " + baseUri);
+ System.out.println("Started secure server at: " + baseUri);
+ } catch (IOException | ProcessingException e) {
+ throw new ServiceConfigurationError(
+ "Make sure you gave a valid address in the config file! (Assignable to this JVM and not in use already)",
+ e);
+ }
+ }
+
+ private void configureServer(HttpServer server) {
+ // Add swagger UI to the server
+ final HttpHandler httpHandler = new CLStaticHttpHandler(HttpServer.class.getClassLoader(), "/swagger/");
+ server.getServerConfiguration().addHttpHandler(httpHandler, "/api");
+ // Allow message payload for GET and DELETE requests - ONLY to provide custom
+ // error message for them
+ server.getServerConfiguration().setAllowPayloadForUndefinedHttpMethods(true);
+ }
+
+ private void shutdown() {
+ useSRService(false);
+ DatabaseManager.closeSessionFactory();
+ if (server != null) {
+ log.info("Stopping server at: " + baseUri);
+ server.shutdownNow();
+ }
+ System.out.println(coreSystem + " Server stopped");
+ System.exit(0);
+ }
+
+ protected void startUaServer (String endpoint) {
+ try {
+ int port = props.getIntProperty("opcua_port", coreSystem.getOpcUaPort());
+ arrowheaduaserver = ArrowheadOpcUaServer.getInstance(port, endpoint);
+ arrowheaduaserver.startup().get();
+ final CompletableFuture future = new CompletableFuture<>();
+ Runtime.getRuntime().addShutdownHook(new Thread(() -> future.complete(null)));
+ // future.get();
+ System.out.println("Starting an opc ua server");
+ } catch (Exception e) {
+ // TODO Auto-generated catch block
+ e.printStackTrace();
+ }
+ }
+
+ private void useSRService(boolean registering) {
+ // Preparing the payload
+ final URI uri = UriBuilder.fromUri(baseUri).build();
+ final boolean isSecure = uri.getScheme().equals("https");
+ final String interfaceName = isSecure ? "HTTP-SECURE-JSON" : "HTTP-INSECURE-JSON";
+ final ArrowheadSystem provider = new ArrowheadSystem(coreSystem.name(), uri.getHost(), uri.getPort(),
+ base64PublicKey);
+
+ for (CoreSystemService service : coreSystem.getServices()) {
+ ArrowheadService providedService = new ArrowheadService(Utility.createSD(service.getServiceDef(), isSecure),
+ Collections.singleton(interfaceName), null);
+ if (isSecure) {
+ providedService.setServiceMetadata(ArrowheadMain.secureServerMetadata);
+ }
+ ServiceRegistryEntry srEntry = new ServiceRegistryEntry(providedService, provider, service.getServiceURI());
+
+ if (registering) {
+ try {
+ Utility.sendRequest(UriBuilder.fromUri(srBaseUri).path("register").build().toString(), "POST",
+ srEntry);
+ } catch (ArrowheadException e) {
+ if (e.getExceptionType() == ExceptionType.DUPLICATE_ENTRY) {
+ Utility.sendRequest(UriBuilder.fromUri(srBaseUri).path("remove").build().toString(), "PUT",
+ srEntry);
+ Utility.sendRequest(UriBuilder.fromUri(srBaseUri).path("register").build().toString(), "POST",
+ srEntry);
+ } else if (e.getExceptionType() == ExceptionType.UNAVAILABLE) {
+ System.out.println("Service Registry is unavailable at the moment, retrying in 15 seconds...");
+ try {
+ Thread.sleep(15000);
+ if (registeringTries == 3) {
+ throw e;
+ } else {
+ registeringTries++;
+ // noinspection ConstantConditions
+ useSRService(registering);
+ }
+ } catch (InterruptedException e1) {
+ e1.printStackTrace();
+ }
+ } else {
+ throw new ArrowheadException(service.getServiceDef() + " service registration failed.", e);
+ }
+ }
+ registeringTries = 1;
+ } else {
+ Utility.sendRequest(UriBuilder.fromUri(srBaseUri).path("remove").build().toString(), "PUT", srEntry);
+ }
+ }
+ }
+
+ private String[] addSwaggerToPackages(String[] packages) {
+ packages = Arrays.copyOf(packages, packages.length + 1);
+ packages[packages.length - 1] = "io.swagger.v3.jaxrs2.integration.resources";
+ return packages;
+ }
+
+ public ArrowheadOpcUaServer getArrowheadOpcUaServer() {
+ return arrowheaduaserver;
+ }
+}
diff --git a/core-common/src/main/java/eu/arrowhead/common/DatabaseManager.java b/core-common/src/main/java/eu/arrowhead/common/DatabaseManager.java
new file mode 100644
index 00000000..f87dda70
--- /dev/null
+++ b/core-common/src/main/java/eu/arrowhead/common/DatabaseManager.java
@@ -0,0 +1,680 @@
+/*
+ * This work is part of the Productive 4.0 innovation project, which receives grants from the
+ * European Commissions H2020 research and innovation programme, ECSEL Joint Undertaking
+ * (project no. 737459), the free state of Saxony, the German Federal Ministry of Education and
+ * national funding authorities from involved countries.
+ */
+
+package eu.arrowhead.common;
+
+import eu.arrowhead.common.exception.ArrowheadException;
+import eu.arrowhead.common.exception.DuplicateEntryException;
+import eu.arrowhead.common.misc.TypeSafeProperties;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+import java.util.Map.Entry;
+import java.util.Optional;
+import java.util.ServiceConfigurationError;
+import java.util.Set;
+import javax.persistence.PersistenceException;
+import javax.ws.rs.core.Response.Status;
+import org.apache.log4j.Logger;
+import org.hibernate.Criteria;
+import org.hibernate.Session;
+import org.hibernate.SessionFactory;
+import org.hibernate.Transaction;
+import org.hibernate.cfg.Configuration;
+import org.hibernate.criterion.Disjunction;
+import org.hibernate.criterion.Restrictions;
+import org.hibernate.exception.ConstraintViolationException;
+import org.hibernate.query.Query;
+
+//NOTE should move to EntityManager from Sessions, using the JPA criteria API (Hibernate criteria will be removed in
+// Hibernate 6)
+public class DatabaseManager {
+
+ private static volatile DatabaseManager instance;
+ private static SessionFactory sessionFactory;
+ private static TypeSafeProperties prop = Utility.getProp();
+ private static String dbAddress;
+ private static String dbUser;
+ private static String dbPassword;
+ private static final Logger log = Logger.getLogger(DatabaseManager.class.getName());
+
+ static {
+ if (prop.containsKey("db_address") || prop.containsKey("log4j.appender.DB.URL")) {
+ if (prop.containsKey("db_address")) {
+ dbAddress = prop.getProperty("db_address");
+ dbUser = prop.getProperty("db_user");
+ dbPassword = prop.getProperty("db_password");
+ } else {
+ dbAddress = prop.getProperty("log4j.appender.DB.URL", "jdbc:mysql://127.0.0.1:3306/log");
+ dbUser = prop.getProperty("log4j.appender.DB.user", "root");
+ dbPassword = prop.getProperty("log4j.appender.DB.password", "root");
+ }
+
+ try {
+ Configuration configuration = new Configuration().configure("hibernate.cfg.xml")
+ .setProperty("hibernate.connection.url", dbAddress)
+ .setProperty("hibernate.connection.username", dbUser)
+ .setProperty("hibernate.connection.password", dbPassword);
+ sessionFactory = configuration.buildSessionFactory();
+ } catch (Exception e) {
+ throw new ServiceConfigurationError(
+ "Database connection could not be established, check default.conf/app.conf files!", e);
+ }
+ }
+ }
+
+ private DatabaseManager() {
+ }
+
+ public static synchronized void init() {
+ if (instance == null) {
+ instance = new DatabaseManager();
+ }
+ }
+
+ public static synchronized DatabaseManager getInstance() {
+ if (instance == null) {
+ instance = new DatabaseManager();
+ }
+ return instance;
+ }
+
+ private synchronized SessionFactory getSessionFactory() {
+ if (sessionFactory == null) {
+ Configuration configuration = new Configuration().configure("hibernate.cfg.xml")
+ .setProperty("hibernate.connection.url", dbAddress)
+ .setProperty("hibernate.connection.username", dbUser)
+ .setProperty("hibernate.connection.password", dbPassword);
+ sessionFactory = configuration.buildSessionFactory();
+ }
+ return sessionFactory;
+ }
+
+ public static synchronized void closeSessionFactory() {
+ if (sessionFactory != null) {
+ sessionFactory.close();
+ }
+ instance = null;
+ }
+
+ public Optional get(Class queryClass, long id) {
+ T object;
+ Transaction transaction = null;
+
+ try (Session session = getSessionFactory().openSession()) {
+ transaction = session.beginTransaction();
+ object = session.get(queryClass, id);
+ transaction.commit();
+ } catch (Exception e) {
+ if (transaction != null) {
+ transaction.rollback();
+ }
+ throw e;
+ }
+
+ return Optional.ofNullable(object);
+ }
+
+ public List get(Class queryClass, Set ids) {
+ List retrievedList = new ArrayList<>();
+ Transaction transaction = null;
+
+ try (Session session = getSessionFactory().openSession()) {
+ transaction = session.beginTransaction();
+ for (Long id : ids) {
+ retrievedList.add(session.get(queryClass, id));
+ }
+ transaction.commit();
+ } catch (Exception e) {
+ if (transaction != null) {
+ transaction.rollback();
+ }
+ throw e;
+ }
+
+ return retrievedList;
+ }
+
+ @SuppressWarnings("unchecked")
+ public T get(Class queryClass, Map restrictionMap) {
+ T object;
+ Transaction transaction = null;
+
+ try (Session session = getSessionFactory().openSession()) {
+ transaction = session.beginTransaction();
+ //NOTE session.createCriteria will be removed in Hibernate 6
+ //noinspection deprecation
+ Criteria criteria = session.createCriteria(queryClass);
+ if (restrictionMap != null && !restrictionMap.isEmpty()) {
+ for (Entry entry : restrictionMap.entrySet()) {
+ criteria.add(Restrictions.eq(entry.getKey(), entry.getValue()));
+ }
+ }
+ object = (T) criteria.uniqueResult();
+ transaction.commit();
+ } catch (Exception e) {
+ log.error("get throws exception: " + e.getMessage(), e);
+ if (transaction != null) {
+ transaction.rollback();
+ }
+ throw e;
+ }
+
+ return object;
+ }
+
+ @SuppressWarnings("unchecked")
+ public List getAll(Class queryClass, Map restrictionMap) {
+ List retrievedList;
+ Transaction transaction = null;
+
+ try (Session session = getSessionFactory().openSession()) {
+ transaction = session.beginTransaction();
+ //NOTE session.createCriteria will be removed in Hibernate 6
+ //noinspection deprecation
+ Criteria criteria = session.createCriteria(queryClass);
+ if (restrictionMap != null && !restrictionMap.isEmpty()) {
+ for (Entry entry : restrictionMap.entrySet()) {
+ criteria.add(Restrictions.eq(entry.getKey(), entry.getValue()));
+ }
+ }
+ retrievedList = (List) criteria.setResultTransformer(Criteria.DISTINCT_ROOT_ENTITY).list();
+ transaction.commit();
+ } catch (Exception e) {
+ log.error("getAll throws exception: " + e.getMessage(), e);
+ if (transaction != null) {
+ transaction.rollback();
+ }
+ throw e;
+ }
+
+ return retrievedList;
+ }
+
+ @SuppressWarnings("unchecked")
+ public List getAllOfEither(Class queryClass, Map restrictionMap) {
+ List retrievedList;
+ Transaction transaction = null;
+
+ try (Session session = getSessionFactory().openSession()) {
+ transaction = session.beginTransaction();
+ //NOTE session.createCriteria will be removed in Hibernate 6
+ //noinspection deprecation
+ Criteria criteria = session.createCriteria(queryClass);
+ if (restrictionMap != null && !restrictionMap.isEmpty()) {
+ Disjunction disjunction = Restrictions.disjunction();
+ for (Entry entry : restrictionMap.entrySet()) {
+ disjunction.add(Restrictions.eq(entry.getKey(), entry.getValue()));
+ }
+ criteria.add(disjunction);
+ }
+ retrievedList = (List) criteria.setResultTransformer(Criteria.DISTINCT_ROOT_ENTITY).list();
+ transaction.commit();
+ } catch (Exception e) {
+ if (transaction != null) {
+ transaction.rollback();
+ }
+ throw e;
+ }
+
+ return retrievedList;
+ }
+
+
+ @SafeVarargs
+ public final T save(T... objects) {
+ Transaction transaction = null;
+
+ try (Session session = getSessionFactory().openSession()) {
+ transaction = session.beginTransaction();
+ for (T object : objects) {
+ session.save(object);
+ }
+ transaction.commit();
+ } catch (PersistenceException e) {
+ if (transaction != null) {
+ transaction.rollback();
+ }
+ Throwable cause = e.getCause();
+ if (cause instanceof ConstraintViolationException && cause.getMessage().equals("could not execute statement")) {
+ log.error("DatabaseManager:save throws DuplicateEntryException", e);
+ throw new DuplicateEntryException(
+ "There is already an entry in the database with these parameters. Please check the unique fields of the "
+ + objects.getClass(), Status.BAD_REQUEST.getStatusCode(), e);
+ } else {
+ Throwable rootCause = Utility.getExceptionRootCause(e);
+ log.error("Unknown exception during database save: " + rootCause.getClass() + " - " + rootCause.getMessage(),
+ e);
+ throw new ArrowheadException(
+ "Unknown exception during database save: " + rootCause.getClass() + " - " + rootCause.getMessage(), e);
+ }
+ } catch (Exception e) {
+ if (transaction != null) {
+ transaction.rollback();
+ }
+ log.error("Unknown exception during database save", e);
+ e.printStackTrace();
+ throw e;
+ }
+
+ return objects[0];
+ }
+
+
+ @SafeVarargs
+ public final T merge(T... objects) {
+ Transaction transaction = null;
+
+ try (Session session = getSessionFactory().openSession()) {
+ transaction = session.beginTransaction();
+ for (T object : objects) {
+ session.merge(object);
+ }
+ transaction.commit();
+ } catch (PersistenceException e) {
+ if (transaction != null) {
+ transaction.rollback();
+ }
+
+ Throwable cause = e.getCause();
+ if (cause instanceof ConstraintViolationException && cause.getMessage().equals("could not execute statement")) {
+ log.error("DatabaseManager:save merge DuplicateEntryException", e);
+ throw new DuplicateEntryException(
+ "There is already an entry in the database with these parameters. Please check the unique fields of the "
+ + objects.getClass(), Status.BAD_REQUEST.getStatusCode(), e);
+ } else {
+ Throwable rootCause = Utility.getExceptionRootCause(e);
+ log.error("Unknown exception during database merge: " + rootCause.getClass() + " - " + rootCause.getMessage(),
+ e);
+ throw new ArrowheadException(
+ "Unknown exception during database merge: " + rootCause.getClass() + " - " + rootCause.getMessage(), e);
+ }
+ } catch (Exception e) {
+ if (transaction != null) {
+ transaction.rollback();
+ }
+ log.error("Unknown exception during database merge", e);
+ throw e;
+ }
+
+ return objects[0];
+ }
+
+ @SafeVarargs
+ public final void delete(T... objects) {
+ Transaction transaction = null;
+
+ try (Session session = getSessionFactory().openSession()) {
+ transaction = session.beginTransaction();
+ for (T object : objects) {
+ session.delete(object);
+ }
+ transaction.commit();
+ } catch (PersistenceException e) {
+ if (transaction != null) {
+ transaction.rollback();
+ }
+
+ Throwable cause = e.getCause();
+ if (cause instanceof ConstraintViolationException && cause.getMessage().equals("could not execute statement")) {
+ log.error("DatabaseManager:delete throws ConstraintViolationException", e);
+ throw new ArrowheadException(
+ "There is a reference to this object in another table, which prevents the delete operation. (" + objects
+ .getClass() + ")", Status.BAD_REQUEST.getStatusCode(), e);
+ } else {
+ Throwable rootCause = Utility.getExceptionRootCause(e);
+ log.error("Unknown exception during database delete: " + rootCause.getClass() + " - " + rootCause.getMessage(),
+ e);
+ throw new ArrowheadException(
+ "Unknown exception during database delete: " + rootCause.getClass() + " - " + rootCause.getMessage(), e);
+ }
+ } catch (Exception e) {
+ if (transaction != null) {
+ transaction.rollback();
+ }
+ log.error("Unknown exception during database delete", e);
+ throw e;
+ }
+ }
+
+ // NOTE this only works well on tables which dont have any connection to any other tables (HQL does not do cascading)
+ @SuppressWarnings("unused")
+ public void deleteAll(String tableName) {
+ Session session = getSessionFactory().openSession();
+ String stringQuery = "DELETE * FROM " + tableName;
+ Query query = session.createQuery(stringQuery);
+ query.executeUpdate();
+ }
+
+}
+
+
+// ----OLD VERSION----
+
+/*
+ * This work is part of the Productive 4.0 innovation project, which receives grants from the
+ * European Commissions H2020 research and innovation programme, ECSEL Joint Undertaking
+ * (project no. 737459), the free state of Saxony, the German Federal Ministry of Education and
+ * national funding authorities from involved countries.
+ */
+/*
+package eu.arrowhead.common;
+
+import eu.arrowhead.common.exception.ArrowheadException;
+import eu.arrowhead.common.exception.DuplicateEntryException;
+import eu.arrowhead.common.misc.TypeSafeProperties;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+import java.util.Map.Entry;
+import java.util.Optional;
+import java.util.ServiceConfigurationError;
+import java.util.Set;
+import javax.persistence.PersistenceException;
+import javax.ws.rs.core.Response.Status;
+import org.apache.log4j.Logger;
+import org.hibernate.Criteria;
+import org.hibernate.Session;
+import org.hibernate.SessionFactory;
+import org.hibernate.Transaction;
+import org.hibernate.cfg.Configuration;
+import org.hibernate.criterion.Disjunction;
+import org.hibernate.criterion.Restrictions;
+import org.hibernate.exception.ConstraintViolationException;
+import org.hibernate.query.Query;
+
+//NOTE should move to EntityManager from Sessions, using the JPA criteria API (Hibernate criteria will be removed in Hibernate 6)
+public class DatabaseManager {
+
+ private static volatile DatabaseManager instance;
+ private static SessionFactory sessionFactory;
+ private static TypeSafeProperties prop = Utility.getProp();
+ private static String dbAddress;
+ private static String dbUser;
+ private static String dbPassword;
+ private static final Logger log = Logger.getLogger(DatabaseManager.class.getName());
+
+ static {
+ if (prop.containsKey("db_address") || prop.containsKey("log4j.appender.DB.URL")) {
+ if (prop.containsKey("db_address")) {
+ dbAddress = prop.getProperty("db_address");
+ dbUser = prop.getProperty("db_user");
+ dbPassword = prop.getProperty("db_password");
+ } else {
+ dbAddress = prop.getProperty("log4j.appender.DB.URL", "jdbc:mysql://127.0.0.1:3306/log");
+ dbUser = prop.getProperty("log4j.appender.DB.user", "root");
+ dbPassword = prop.getProperty("log4j.appender.DB.password", "root");
+ }
+
+ try {
+ Configuration configuration = new Configuration().configure("hibernate.cfg.xml")
+ .setProperty("hibernate.connection.url", dbAddress)
+ .setProperty("hibernate.connection.username", dbUser)
+ .setProperty("hibernate.connection.password", dbPassword);
+ sessionFactory = configuration.buildSessionFactory();
+ } catch (Exception e) {
+ throw new ServiceConfigurationError(
+ "Database connection could not be established, check default.conf/app.conf files!", e);
+ }
+ }
+ }
+
+ private DatabaseManager() {
+ }
+
+ public static synchronized void init() {
+ if (instance == null) {
+ instance = new DatabaseManager();
+ }
+ }
+
+ public static synchronized DatabaseManager getInstance() {
+ if (instance == null) {
+ instance = new DatabaseManager();
+ }
+ return instance;
+ }
+
+ private synchronized SessionFactory getSessionFactory() {
+ if (sessionFactory == null) {
+ Configuration configuration = new Configuration().configure("hibernate.cfg.xml")
+ .setProperty("hibernate.connection.url", dbAddress)
+ .setProperty("hibernate.connection.username", dbUser)
+ .setProperty("hibernate.connection.password", dbPassword);
+ sessionFactory = configuration.buildSessionFactory();
+ }
+ return sessionFactory;
+ }
+
+ public static synchronized void closeSessionFactory() {
+ if (sessionFactory != null) {
+ sessionFactory.close();
+ }
+ instance = null;
+ }
+
+ public Optional get(Class queryClass, long id) {
+ T object;
+ Transaction transaction = null;
+
+ try (Session session = getSessionFactory().openSession()) {
+ transaction = session.beginTransaction();
+ object = session.get(queryClass, id);
+ transaction.commit();
+ } catch (Exception e) {
+ if (transaction != null) {
+ transaction.rollback();
+ }
+ throw e;
+ }
+
+ return Optional.ofNullable(object);
+ }
+
+ public List get(Class queryClass, Set ids) {
+ List retrievedList = new ArrayList<>();
+ Transaction transaction = null;
+
+ try (Session session = getSessionFactory().openSession()) {
+ transaction = session.beginTransaction();
+ for (Long id : ids) {
+ retrievedList.add(session.get(queryClass, id));
+ }
+ transaction.commit();
+ } catch (Exception e) {
+ if (transaction != null) {
+ transaction.rollback();
+ }
+ throw e;
+ }
+
+ return retrievedList;
+ }
+
+ @SuppressWarnings("unchecked")
+ public T get(Class queryClass, Map restrictionMap) {
+ T object;
+ Transaction transaction = null;
+
+ try (Session session = getSessionFactory().openSession()) {
+ transaction = session.beginTransaction();
+ // NOTE session.createCriteria will be removed in Hibernate 6
+ // noinspection deprecation
+ Criteria criteria = session.createCriteria(queryClass);
+ if (restrictionMap != null && !restrictionMap.isEmpty()) {
+ for (Entry entry : restrictionMap.entrySet()) {
+ criteria.add(Restrictions.eq(entry.getKey(), entry.getValue()));
+ }
+ }
+ object = (T) criteria.uniqueResult();
+ transaction.commit();
+ } catch (Exception e) {
+ log.error("get throws exception: " + e.getMessage(), e);
+ if (transaction != null) {
+ transaction.rollback();
+ }
+ throw e;
+ }
+
+ return object;
+ }
+
+ @SuppressWarnings("unchecked")
+ public List getAll(Class queryClass, Map restrictionMap) {
+ List retrievedList;
+ Transaction transaction = null;
+
+ try (Session session = getSessionFactory().openSession()) {
+ transaction = session.beginTransaction();
+ // NOTE session.createCriteria will be removed in Hibernate 6
+ // noinspection deprecation
+ Criteria criteria = session.createCriteria(queryClass);
+ if (restrictionMap != null && !restrictionMap.isEmpty()) {
+ for (Entry entry : restrictionMap.entrySet()) {
+ criteria.add(Restrictions.eq(entry.getKey(), entry.getValue()));
+ }
+ }
+ retrievedList = (List) criteria.setResultTransformer(Criteria.DISTINCT_ROOT_ENTITY).list();
+ transaction.commit();
+ } catch (Exception e) {
+ log.error("getAll throws exception: " + e.getMessage(), e);
+ if (transaction != null) {
+ transaction.rollback();
+ }
+ throw e;
+ }
+
+ return retrievedList;
+ }
+
+ @SuppressWarnings("unchecked")
+ public List getAllOfEither(Class queryClass, Map restrictionMap) {
+ List retrievedList;
+ Transaction transaction = null;
+
+ try (Session session = getSessionFactory().openSession()) {
+ transaction = session.beginTransaction();
+ // NOTE session.createCriteria will be removed in Hibernate 6
+ // noinspection deprecation
+ Criteria criteria = session.createCriteria(queryClass);
+ if (restrictionMap != null && !restrictionMap.isEmpty()) {
+ Disjunction disjunction = Restrictions.disjunction();
+ for (Entry entry : restrictionMap.entrySet()) {
+ disjunction.add(Restrictions.eq(entry.getKey(), entry.getValue()));
+ }
+ criteria.add(disjunction);
+ }
+ retrievedList = (List) criteria.setResultTransformer(Criteria.DISTINCT_ROOT_ENTITY).list();
+ transaction.commit();
+ } catch (Exception e) {
+ if (transaction != null) {
+ transaction.rollback();
+ }
+ throw e;
+ }
+
+ return retrievedList;
+ }
+
+ @SafeVarargs
+ public final T save(T... objects) {
+ Transaction transaction = null;
+
+ try (Session session = getSessionFactory().openSession()) {
+ transaction = session.beginTransaction();
+ for (T object : objects) {
+ session.save(object);
+ }
+ transaction.commit();
+ } catch (PersistenceException e) {
+ if (transaction != null) {
+ transaction.rollback();
+ }
+ // NOTE root cause of persistenceException can be other reasons too
+ log.error("DatabaseManager:save throws DuplicateEntryException", e);
+ throw new DuplicateEntryException(
+ "There is already an entry in the database with these parameters. Please check the unique fields of the "
+ + objects.getClass(),
+ Status.BAD_REQUEST.getStatusCode(), e);
+ } catch (Exception e) {
+ if (transaction != null) {
+ transaction.rollback();
+ }
+ throw e;
+ }
+
+ return objects[0];
+ }
+
+ @SafeVarargs
+ public final T merge(T... objects) {
+ Transaction transaction = null;
+
+ try (Session session = getSessionFactory().openSession()) {
+ transaction = session.beginTransaction();
+ for (T object : objects) {
+ session.merge(object);
+ }
+ transaction.commit();
+ } catch (PersistenceException e) {
+ if (transaction != null) {
+ transaction.rollback();
+ }
+ log.error("DatabaseManager:merge throws DuplicateEntryException", e);
+ throw new DuplicateEntryException(
+ "There is already an entry in the database with these parameters. Please check the unique fields of the "
+ + objects.getClass(),
+ Status.BAD_REQUEST.getStatusCode(), e);
+ } catch (Exception e) {
+ if (transaction != null) {
+ transaction.rollback();
+ }
+ throw e;
+ }
+
+ return objects[0];
+ }
+
+ @SafeVarargs
+ public final void delete(T... objects) {
+ Transaction transaction = null;
+
+ try (Session session = getSessionFactory().openSession()) {
+ transaction = session.beginTransaction();
+ for (T object : objects) {
+ session.delete(object);
+ }
+ transaction.commit();
+ } catch (ConstraintViolationException e) {
+ if (transaction != null) {
+ transaction.rollback();
+ }
+ log.error("DatabaseManager:delete throws ConstraintViolationException");
+ throw new ArrowheadException(
+ "There is a reference to this object in another table, which prevents the delete operation. ("
+ + objects.getClass() + ")",
+ Status.BAD_REQUEST.getStatusCode(), e);
+ } catch (Exception e) {
+ if (transaction != null) {
+ transaction.rollback();
+ }
+ throw e;
+ }
+ }
+
+ // NOTE this only works well on tables which dont have any connection to any
+ // other tables (HQL does not do cascading)
+ @SuppressWarnings("unused")
+ public void deleteAll(String tableName) {
+ Session session = getSessionFactory().openSession();
+ String stringQuery = "DELETE * FROM " + tableName;
+ Query query = session.createQuery(stringQuery);
+ query.executeUpdate();
+ }
+}
+
+*/
\ No newline at end of file
diff --git a/core-common/src/main/java/eu/arrowhead/common/Utility.java b/core-common/src/main/java/eu/arrowhead/common/Utility.java
new file mode 100644
index 00000000..d0acb328
--- /dev/null
+++ b/core-common/src/main/java/eu/arrowhead/common/Utility.java
@@ -0,0 +1,576 @@
+/*
+ * This work is part of the Productive 4.0 innovation project, which receives grants from the
+ * European Commissions H2020 research and innovation programme, ECSEL Joint Undertaking
+ * (project no. 737459), the free state of Saxony, the German Federal Ministry of Education and
+ * national funding authorities from involved countries.
+ */
+
+package eu.arrowhead.common;
+
+import com.fasterxml.jackson.databind.ObjectMapper;
+import eu.arrowhead.common.database.ArrowheadCloud;
+import eu.arrowhead.common.database.ArrowheadService;
+import eu.arrowhead.common.database.ArrowheadSystem;
+import eu.arrowhead.common.database.NeighborCloud;
+import eu.arrowhead.common.database.OwnCloud;
+import eu.arrowhead.common.database.ServiceRegistryEntry;
+import eu.arrowhead.common.exception.ArrowheadException;
+import eu.arrowhead.common.exception.AuthException;
+import eu.arrowhead.common.exception.BadPayloadException;
+import eu.arrowhead.common.exception.DataNotFoundException;
+import eu.arrowhead.common.exception.DnsException;
+import eu.arrowhead.common.exception.DuplicateEntryException;
+import eu.arrowhead.common.exception.ErrorMessage;
+import eu.arrowhead.common.exception.UnavailableServerException;
+import eu.arrowhead.common.json.JacksonJsonProviderAtRest;
+import eu.arrowhead.common.messages.ServiceQueryForm;
+import eu.arrowhead.common.messages.ServiceQueryResult;
+import eu.arrowhead.common.misc.CoreSystemService;
+import eu.arrowhead.common.misc.TypeSafeProperties;
+import java.io.BufferedReader;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.io.UnsupportedEncodingException;
+import java.net.InetAddress;
+import java.net.NetworkInterface;
+import java.net.SocketException;
+import java.net.URI;
+import java.net.URISyntaxException;
+import java.nio.charset.StandardCharsets;
+import java.nio.file.Files;
+import java.nio.file.Paths;
+import java.time.ZoneId;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Enumeration;
+import java.util.List;
+import java.util.Optional;
+import java.util.ServiceConfigurationError;
+import java.util.Set;
+import java.util.stream.Collectors;
+import javax.net.ssl.HostnameVerifier;
+import javax.net.ssl.SSLContext;
+import javax.validation.ConstraintViolation;
+import javax.validation.Validation;
+import javax.validation.Validator;
+import javax.validation.ValidatorFactory;
+import javax.ws.rs.NotAllowedException;
+import javax.ws.rs.ProcessingException;
+import javax.ws.rs.client.Client;
+import javax.ws.rs.client.ClientBuilder;
+import javax.ws.rs.client.Entity;
+import javax.ws.rs.client.Invocation.Builder;
+import javax.ws.rs.core.Response;
+import javax.ws.rs.core.Response.Status;
+import javax.ws.rs.core.Response.Status.Family;
+import javax.ws.rs.core.UriBuilder;
+import org.apache.log4j.Logger;
+import org.glassfish.jersey.client.ClientConfig;
+import org.glassfish.jersey.client.ClientProperties;
+
+public final class Utility {
+
+ private static Client client = createClient(null);
+ private static Client sslClient;
+ private static SSLContext sslContext;
+ private static String SR_QUERY_URI;
+
+ private static final ObjectMapper mapper = JacksonJsonProviderAtRest.getMapper();
+ private static final Logger log = Logger.getLogger(Utility.class.getName());
+ private static final HostnameVerifier allHostsValid = (hostname, session) -> {
+ // Decide whether to allow the connection...
+ return true;
+ };
+
+ private static final String DEFAULT_CONF = "default.conf";
+ private static final String DEFAULT_CONF_DIR = "config" + File.separator + "default.conf";
+ private static final String APP_CONF = "app.conf";
+ private static final String APP_CONF_DIR = "config" + File.separator + "app.conf";
+
+ private Utility() throws AssertionError {
+ throw new AssertionError("Arrowhead Common:Utility is a non-instantiable class");
+ }
+
+ private static Client createClient(SSLContext context) {
+ ClientConfig configuration = new ClientConfig();
+ configuration.property(ClientProperties.CONNECT_TIMEOUT, 30000);
+ configuration.property(ClientProperties.READ_TIMEOUT, 30000);
+
+ Client client;
+ if (context != null) {
+ client = ClientBuilder.newBuilder().sslContext(context).withConfig(configuration).hostnameVerifier(allHostsValid)
+ .build();
+ } else {
+ client = ClientBuilder.newClient(configuration);
+ }
+ client.register(JacksonJsonProviderAtRest.class);
+ return client;
+ }
+
+ public static void setSSLContext(SSLContext context) {
+ sslContext = context;
+ sslClient = createClient(sslContext);
+ }
+
+ public static void setServiceRegistryUri(String uri) {
+ if (uri == null) {
+ throw new AssertionError("Arrowhead Common:Utility has no Service Registry URL.");
+ }
+ SR_QUERY_URI = UriBuilder.fromUri(uri).path("query").build().toString();
+ }
+
+ public static Response sendRequest(String uri, String method, T payload, SSLContext givenContext) {
+ log.info("Sending " + method + " request to: " + uri);
+
+ boolean isSecure = false;
+ if (uri == null) {
+ log.error("sendRequest received null uri");
+ throw new NullPointerException(
+ "send (HTTP) request method received null URL. This most likely means the invoking Core System could not "
+ + "fetch the service"
+ + " of another Core System from the Service Registry!");
+ }
+ if (uri.startsWith("https")) {
+ isSecure = true;
+ }
+
+ if (isSecure && sslClient == null) {
+ throw new AuthException(
+ "SSL Context is not set, but secure request sending was invoked. An insecure module can not send requests "
+ + "to secure modules.",
+ Status.UNAUTHORIZED.getStatusCode());
+ }
+ Client usedClient = isSecure ? givenContext != null ? createClient(givenContext) : sslClient : client;
+
+ Builder request = usedClient.target(UriBuilder.fromUri(uri).build()).request()
+ .header("Content-type", "application/json");
+ Response response; // will not be null after the switch-case
+ try {
+ switch (method) {
+ case "GET":
+ response = request.get();
+ break;
+ case "POST":
+ response = request.post(Entity.json(payload));
+ break;
+ case "PUT":
+ response = request.put(Entity.json(payload));
+ break;
+ case "DELETE":
+ response = request.delete();
+ break;
+ default:
+ throw new NotAllowedException("Invalid method type was given to the Utility.sendRequest() method");
+ }
+ } catch (ProcessingException e) {
+ if (e.getCause().getMessage().contains("PKIX path")) {
+ log.error("The system at " + uri + " is not part of the same certificate chain of trust!");
+ throw new AuthException("The system at " + uri + " is not part of the same certificate chain of trust!",
+ Status.UNAUTHORIZED.getStatusCode(), e);
+ } else {
+ log.error("UnavailableServerException occurred at " + uri, e);
+ throw new UnavailableServerException("Could not get any response from: " + uri,
+ Status.SERVICE_UNAVAILABLE.getStatusCode(), e);
+ }
+ }
+
+ // If the response status code does not start with 2 the request was not successful
+ if (!(response.getStatusInfo().getFamily() == Family.SUCCESSFUL)) {
+ handleException(response, uri);
+ }
+
+ return response;
+ }
+
+ public static Response sendRequest(String uri, String method, T payload) {
+ return sendRequest(uri, method, payload, null);
+ }
+
+ //TODO option for async request sending which can be used by the event handler
+
+ private static void handleException(Response response, String uri) {
+ //The response body has to be extracted before the stream closes
+ String errorMessageBody = toPrettyJson(null, response.getEntity());
+ if (errorMessageBody == null || errorMessageBody.equals("null")) {
+ response.bufferEntity();
+ errorMessageBody = response.readEntity(String.class);
+ if (errorMessageBody.length() > 250) {
+ errorMessageBody = errorMessageBody.substring(0, 250);
+ }
+ }
+
+ ErrorMessage errorMessage;
+ try {
+ errorMessage = response.readEntity(ErrorMessage.class);
+ } catch (RuntimeException e) {
+ log.error("Unknown reason for RuntimeException at the sendRequest() method.", e);
+ log.info("Request failed, response status code: " + response.getStatus());
+ log.info("Request failed, response body: " + errorMessageBody);
+ throw new RuntimeException("Unknown error occurred at " + uri + ". Check log for possibly more information.", e);
+ }
+ if (errorMessage == null) {
+ log.error("Unknown reason for RuntimeException at the sendRequest() method.");
+ log.info("Request failed, response status code: " + response.getStatus());
+ log.info("Request failed, response body: " + errorMessageBody);
+ throw new RuntimeException("Unknown error occurred at " + uri + ". Check log for possibly more information.");
+ } else if (errorMessage.getExceptionType() == null) {
+ log.info("Request failed, response status code: " + response.getStatus());
+ log.info("Request failed, response body: " + errorMessageBody);
+ throw new RuntimeException("Unknown error occurred at " + uri + ". Check log for possibly more information.");
+ } else {
+ log.error("Request returned with " + errorMessage.getExceptionType().toString() + ": " + errorMessage
+ .getErrorMessage());
+ switch (errorMessage.getExceptionType()) {
+ case ARROWHEAD:
+ throw new ArrowheadException(errorMessage.getErrorMessage(), errorMessage.getErrorCode(),
+ errorMessage.getOrigin());
+ case AUTH:
+ throw new AuthException(errorMessage.getErrorMessage(), errorMessage.getErrorCode(),
+ errorMessage.getOrigin());
+ case BAD_MEDIA_TYPE:
+ throw new ArrowheadException(errorMessage.getErrorMessage(), errorMessage.getErrorCode());
+ case BAD_METHOD:
+ throw new ArrowheadException(errorMessage.getErrorMessage(), errorMessage.getErrorCode(),
+ errorMessage.getOrigin());
+ case BAD_PAYLOAD:
+ throw new BadPayloadException(errorMessage.getErrorMessage(), errorMessage.getErrorCode(),
+ errorMessage.getOrigin());
+ case BAD_URI:
+ throw new ArrowheadException(errorMessage.getErrorMessage(), errorMessage.getErrorCode(),
+ errorMessage.getOrigin());
+ case DATA_NOT_FOUND:
+ throw new DataNotFoundException(errorMessage.getErrorMessage(), errorMessage.getErrorCode(),
+ errorMessage.getOrigin());
+ case DNS_SD:
+ throw new DnsException(errorMessage.getErrorMessage(), errorMessage.getErrorCode(), errorMessage.getOrigin());
+ case DUPLICATE_ENTRY:
+ throw new DuplicateEntryException(errorMessage.getErrorMessage(), errorMessage.getErrorCode(),
+ errorMessage.getOrigin());
+ case GENERIC:
+ throw new ArrowheadException(errorMessage.getErrorMessage(), errorMessage.getErrorCode(),
+ errorMessage.getOrigin());
+ case JSON_PROCESSING:
+ throw new ArrowheadException(errorMessage.getErrorMessage(), errorMessage.getErrorCode(),
+ errorMessage.getOrigin());
+ case UNAVAILABLE:
+ throw new UnavailableServerException(errorMessage.getErrorMessage(), errorMessage.getErrorCode(),
+ errorMessage.getOrigin());
+ }
+ }
+ }
+
+ public static String getUri(String address, int port, String serviceURI, boolean isSecure, boolean serverStart) {
+ if (address == null) {
+ log.error("Address can not be null (Utility:getUri throws NPE)");
+ throw new NullPointerException("Address can not be null (Utility:getUri throws NPE)");
+ }
+
+ UriBuilder ub = UriBuilder.fromPath("").host(address);
+ if (isSecure) {
+ ub.scheme("https");
+ } else {
+ ub.scheme("http");
+ }
+ if (port > 0) {
+ ub.port(port);
+ }
+ if (serviceURI != null) {
+ ub.path(serviceURI);
+ }
+
+ String url = ub.toString();
+ try {
+ new URI(url);
+ } catch (URISyntaxException e) {
+ if (serverStart) {
+ throw new ServiceConfigurationError(
+ url + " is not a valid URL to start a HTTP server! Please fix the address field in the properties file.");
+ } else {
+ log.error("Bad URL components passed to getUri() method");
+ throw new ArrowheadException(url + " is not a valid URL!");
+ }
+ }
+
+ log.info("Utility:getUri returning this: " + url);
+ return url;
+ }
+
+ public static Optional getServiceInfo(String serviceId) {
+ ArrowheadService service = sslContext == null ? new ArrowheadService(createSD(serviceId, false),
+ Collections.singleton("HTTP-INSECURE-JSON"),
+ null)
+ : new ArrowheadService(createSD(serviceId, true),
+ Collections.singleton("HTTP-SECURE-JSON"),
+ ArrowheadMain.secureServerMetadata);
+ ServiceQueryForm sqf = new ServiceQueryForm(service, true, false);
+ Response response = sendRequest(SR_QUERY_URI, "PUT", sqf);
+ ServiceQueryResult result = response.readEntity(ServiceQueryResult.class);
+ if (result != null && result.isValid()) {
+ ServiceRegistryEntry entry = result.getServiceQueryData().get(0);
+ ArrowheadSystem coreSystem = entry.getProvider();
+ boolean isSecure = false;
+ if (!entry.getProvidedService().getServiceMetadata().isEmpty()) {
+ isSecure = entry.getProvidedService().getServiceMetadata().containsKey("security");
+ }
+ String serviceURI = getUri(coreSystem.getAddress(), coreSystem.getPort(), entry.getServiceURI(), isSecure, false);
+ if (serviceId.equals(CoreSystemService.GW_CONSUMER_SERVICE.getServiceDef()) || serviceId
+ .equals(CoreSystemService.GW_PROVIDER_SERVICE.getServiceDef())) {
+ return Optional.of(new String[]{serviceURI, coreSystem.getSystemName(), coreSystem.getAddress(),
+ coreSystem.getAuthenticationInfo()});
+ }
+ return Optional.of(new String[]{serviceURI});
+ }
+ return Optional.empty();
+ }
+
+ public static List getNeighborCloudURIs(boolean isSecure) {
+ List cloudList = new ArrayList<>(DatabaseManager.getInstance().getAll(NeighborCloud.class, null));
+
+ List uriList = new ArrayList<>();
+ for (NeighborCloud cloud : cloudList) {
+ if (isSecure == cloud.getCloud().isSecure()) {
+ uriList.add(getUri(cloud.getCloud().getAddress(), cloud.getCloud().getPort(),
+ cloud.getCloud().getGatekeeperServiceURI(), cloud.getCloud().isSecure(), false));
+ }
+ }
+
+ return uriList;
+ }
+
+ public static ArrowheadCloud getOwnCloud(boolean isSecure) {
+ List cloudList = DatabaseManager.getInstance().getAll(OwnCloud.class, null);
+ if (cloudList.isEmpty()) {
+ log.error("Utility:getOwnCloud not found in the database.");
+ throw new DataNotFoundException(
+ "Own Cloud information not found in the database. This information is needed for the Gatekeeper System.",
+ Status.NOT_FOUND.getStatusCode());
+ }
+ if (cloudList.size() > 2) {
+ log.warn("own_cloud table should NOT have more than 2 rows.");
+ }
+ if (isSecure) {
+ for (OwnCloud cloud : cloudList) {
+ if (cloud.getCloud().isSecure()) {
+ return cloud.getCloud();
+ }
+ }
+ log.error("Utility:getOwnCloud finds no secure own cloud!");
+ throw new DataNotFoundException("Could not find secure own cloud information in the database!",
+ Status.NOT_FOUND.getStatusCode());
+ } else {
+ for (OwnCloud cloud : cloudList) {
+ if (!cloud.getCloud().isSecure()) {
+ return cloud.getCloud();
+ }
+ }
+ log.error("Utility:getOwnCloud finds no insecure own cloud!");
+ throw new DataNotFoundException("Could not find insecure own cloud information in the database!",
+ Status.NOT_FOUND.getStatusCode());
+ }
+ }
+
+ public static String stripEndSlash(String uri) {
+ if (uri != null && uri.endsWith("/")) {
+ return uri.substring(0, uri.length() - 1);
+ }
+ return uri;
+ }
+
+ public static String getRequestPayload(InputStream is) {
+ StringBuilder sb = new StringBuilder();
+ String line;
+ try (BufferedReader br = new BufferedReader(new InputStreamReader(is, StandardCharsets.UTF_8))) {
+ while ((line = br.readLine()) != null) {
+ sb.append(line);
+ }
+ } catch (UnsupportedEncodingException e) {
+ log.fatal("getRequestPayload ISReader has unsupported charset set!");
+ throw new AssertionError(
+ "getRequestPayload InputStreamReader has unsupported character set! Code needs to be changed!", e);
+ } catch (IOException e) {
+ log.error("IOException while reading the request payload");
+ throw new RuntimeException("IOException occured while reading an incoming request payload", e);
+ }
+
+ if (!sb.toString().isEmpty()) {
+ String payload = toPrettyJson(sb.toString(), null);
+ return payload != null ? payload : "";
+ } else {
+ return "";
+ }
+ }
+
+ public static String toPrettyJson(String jsonString, Object obj) {
+ try {
+ if (jsonString != null) {
+ jsonString = jsonString.trim();
+ if (jsonString.startsWith("{")) {
+ Object tempObj = mapper.readValue(jsonString, Object.class);
+ return mapper.writeValueAsString(tempObj);
+ } else {
+ Object[] tempObj = mapper.readValue(jsonString, Object[].class);
+ return mapper.writeValueAsString(tempObj);
+ }
+ }
+ if (obj != null) {
+ return mapper.writeValueAsString(obj);
+ }
+ } catch (IOException e) {
+ throw new ArrowheadException(
+ "Jackson library threw IOException during JSON serialization! Wrapping it in RuntimeException. Exception "
+ + "message: " + e.getMessage(), e);
+ }
+ return null;
+ }
+
+ public static T fromJson(String json, Class parsedClass) {
+ try {
+ return mapper.readValue(json, parsedClass);
+ } catch (IOException e) {
+ throw new ArrowheadException("Jackson library threw exception during JSON parsing!", e);
+ }
+ }
+
+ public static String createSD(String baseSD, boolean isSecure) {
+ if (isSecure) {
+ return "Secure" + baseSD;
+ } else {
+ return "Insecure" + baseSD;
+ }
+ }
+
+ public static TypeSafeProperties getProp(String fileName) {
+ TypeSafeProperties prop = new TypeSafeProperties();
+ try {
+ File file;
+ if (new File(fileName).exists()) {
+ file = new File(fileName);
+ } else {
+ file = new File("config" + File.separator + fileName);
+ }
+ FileInputStream inputStream = new FileInputStream(file);
+ prop.load(inputStream);
+ } catch (FileNotFoundException ex) {
+ throw new ServiceConfigurationError(
+ fileName + " file not found, make sure you have the correct working directory "
+ + "set! (directory where the config folder can be found)", ex);
+ } catch (Exception ex) {
+ ex.printStackTrace();
+ }
+ return prop;
+ }
+
+ public static TypeSafeProperties getProp() {
+ TypeSafeProperties prop = new TypeSafeProperties();
+
+ try {
+ if (Files.isReadable(Paths.get(DEFAULT_CONF))) {
+ prop.load(new FileInputStream(DEFAULT_CONF));
+ } else if (Files.isReadable(Paths.get(DEFAULT_CONF_DIR))) {
+ prop.load(new FileInputStream(DEFAULT_CONF_DIR));
+ } else {
+ throw new ServiceConfigurationError(
+ "default.conf file not found in the working directory! (" + System.getProperty("user.dir") + ")");
+ }
+
+ if (Files.isReadable(Paths.get(APP_CONF))) {
+ prop.load(new FileInputStream(APP_CONF));
+ } else if (Files.isReadable(Paths.get(APP_CONF_DIR))) {
+ prop.load(new FileInputStream(APP_CONF_DIR));
+ }
+ } catch (IOException e) {
+ throw new AssertionError("File loading failed...", e);
+ }
+
+ //If MySQL based JDBC URLs are used, we append the system default time zone to the URL
+ //This is for a bug fix with certain MySQL JDBC driver versions: https://github.com/arrowhead-f/core-java/issues/30
+ String timeZoneQueryParam = "serverTimezone=" + ZoneId.systemDefault().getId();
+
+ String dbAddress = prop.getProperty("db_address");
+ if (dbAddress != null && dbAddress.contains("mysql")) {
+ if (dbAddress.contains("?")) {
+ dbAddress = dbAddress + "&" + timeZoneQueryParam;
+ } else {
+ dbAddress = dbAddress + "?" + timeZoneQueryParam;
+ }
+ prop.setProperty("db_address", dbAddress);
+ }
+
+ String logAddress = prop.getProperty("log4j.appender.DB.URL");
+ if (logAddress != null && logAddress.contains("mysql")) {
+ if (logAddress.contains("?")) {
+ logAddress = logAddress + "&" + timeZoneQueryParam;
+ } else {
+ logAddress = logAddress + "?" + timeZoneQueryParam;
+ }
+ prop.setProperty("log4j.appender.DB.URL", logAddress);
+ }
+
+ return prop;
+ }
+
+ public static void checkProperties(Set propertyNames, List mandatoryProperties) {
+ if (mandatoryProperties == null || mandatoryProperties.isEmpty()) {
+ return;
+ }
+ //Arrays.asList() returns immutable lists, so we have to copy it first
+ List properties = new ArrayList<>(mandatoryProperties);
+ if (!propertyNames.containsAll(mandatoryProperties)) {
+ properties.removeIf(propertyNames::contains);
+ throw new ServiceConfigurationError("Missing field(s) from config file: " + properties.toString());
+ }
+ }
+
+ /* If needed, this method can be used to get the IPv4 address of the host machine. Public point-to-point IP
+ addresses are prioritized over private
+ (site local) IP addresses */
+ @SuppressWarnings("unused")
+ public static String getIpAddress() throws SocketException {
+ List addresses = new ArrayList<>();
+
+ Enumeration e = NetworkInterface.getNetworkInterfaces();
+ while (e.hasMoreElements()) {
+ NetworkInterface inf = (NetworkInterface) e.nextElement();
+ Enumeration ee = inf.getInetAddresses();
+ while (ee.hasMoreElements()) {
+ addresses.add((InetAddress) ee.nextElement());
+ }
+ }
+
+ addresses = addresses.stream().filter(current -> !current.getHostAddress().contains(":"))
+ .filter(current -> !current.isLoopbackAddress())
+ .filter(current -> !current.isMulticastAddress())
+ .filter(current -> !current.isLinkLocalAddress()).collect(Collectors.toList());
+ if (addresses.isEmpty()) {
+ throw new SocketException("No valid addresses left after filtering");
+ }
+ for (InetAddress address : addresses) {
+ if (!address.isSiteLocalAddress()) {
+ return address.getHostAddress();
+ }
+ }
+ return addresses.get(0).getHostAddress();
+ }
+
+ public static boolean isBeanValid(T bean) {
+ ValidatorFactory factory = Validation.buildDefaultValidatorFactory();
+ Validator validator = factory.getValidator();
+ Set> violations = validator.validate(bean);
+ return violations.isEmpty();
+ }
+
+ @SuppressWarnings("WeakerAccess")
+ public static Throwable getExceptionRootCause(Throwable e) {
+ Throwable cause = null;
+ Throwable result = e;
+
+ while (null != (cause = result.getCause()) && (result != cause)) {
+ result = cause;
+ }
+ return result;
+ }
+}
diff --git a/core-common/src/main/java/eu/arrowhead/common/database/ArrowheadCloud.java b/core-common/src/main/java/eu/arrowhead/common/database/ArrowheadCloud.java
new file mode 100644
index 00000000..23b86fe6
--- /dev/null
+++ b/core-common/src/main/java/eu/arrowhead/common/database/ArrowheadCloud.java
@@ -0,0 +1,186 @@
+/*
+ * This work is part of the Productive 4.0 innovation project, which receives grants from the
+ * European Commissions H2020 research and innovation programme, ECSEL Joint Undertaking
+ * (project no. 737459), the free state of Saxony, the German Federal Ministry of Education and
+ * national funding authorities from involved countries.
+ */
+
+package eu.arrowhead.common.database;
+
+import com.google.common.base.MoreObjects;
+import java.util.Objects;
+import javax.persistence.Column;
+import javax.persistence.Entity;
+import javax.persistence.GeneratedValue;
+import javax.persistence.GenerationType;
+import javax.persistence.Id;
+import javax.persistence.Table;
+import javax.persistence.UniqueConstraint;
+import javax.validation.constraints.Max;
+import javax.validation.constraints.Min;
+import javax.validation.constraints.NotNull;
+import javax.validation.constraints.Pattern;
+import javax.validation.constraints.Size;
+import org.hibernate.annotations.GenericGenerator;
+import org.hibernate.annotations.Type;
+import org.hibernate.validator.constraints.Length;
+import org.hibernate.validator.constraints.NotBlank;
+
+@Entity
+@Table(name = "arrowhead_cloud", uniqueConstraints = {@UniqueConstraint(columnNames = {"operator", "cloud_name"})})
+public class ArrowheadCloud {
+
+ @Id
+ @GenericGenerator(name = "table_generator", strategy = "org.hibernate.id.enhanced.TableGenerator")
+ @GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "table_generator")
+ private Long id;
+
+ @NotBlank
+ @Length(max = 255, message = "Cloud operator must be 255 character at max")
+ @Pattern(regexp = "[\\p{L}0-9-_:]+", message =
+ "Cloud operator can only contain alphanumerical characters and some special characters (dash, "
+ + "underscore and colon)")
+ private String operator;
+
+ @NotBlank
+ @Column(name = "cloud_name")
+ @Size(max = 255, message = "Cloud name must be 255 character at max")
+ @Pattern(regexp = "[\\p{L}0-9-_:]+", message =
+ "Cloud name can only contain alphanumerical characters and some special characters (dash, "
+ + "underscore and colon)")
+ private String cloudName;
+
+ @NotBlank
+ @Size(min = 3, max = 255, message = "Cloud address must be between 3 and 255 characters")
+ private String address;
+
+ @NotNull
+ @Min(value = 1, message = "Port can not be less than 1")
+ @Max(value = 65535, message = "Port can not be greater than 65535")
+ private Integer port;
+
+ @NotBlank
+ @Column(name = "gatekeeper_service_uri")
+ private String gatekeeperServiceURI;
+
+ @Column(name = "authentication_info")
+ @Size(max = 2047, message = "Authentication information must be 2047 character at max")
+ private String authenticationInfo;
+
+ @Column(name = "is_secure")
+ @Type(type = "yes_no")
+ private Boolean secure = false;
+
+ public ArrowheadCloud() {
+ }
+
+ public ArrowheadCloud(String operator, String cloudName, String address, Integer port, String gatekeeperServiceURI, String authenticationInfo,
+ Boolean secure) {
+ this.operator = operator;
+ this.cloudName = cloudName;
+ this.address = address;
+ this.port = port;
+ this.gatekeeperServiceURI = gatekeeperServiceURI;
+ this.authenticationInfo = authenticationInfo;
+ this.secure = secure;
+ }
+
+ public Long getId() {
+ return id;
+ }
+
+ public void setId(Long id) {
+ this.id = id;
+ }
+
+ public String getOperator() {
+ return operator;
+ }
+
+ public void setOperator(String operator) {
+ this.operator = operator;
+ }
+
+ public String getCloudName() {
+ return cloudName;
+ }
+
+ public void setCloudName(String cloudName) {
+ this.cloudName = cloudName;
+ }
+
+ public String getAddress() {
+ return address;
+ }
+
+ public void setAddress(String address) {
+ this.address = address;
+ }
+
+ public Integer getPort() {
+ return port;
+ }
+
+ public void setPort(Integer port) {
+ this.port = port;
+ }
+
+ public String getGatekeeperServiceURI() {
+ return gatekeeperServiceURI;
+ }
+
+ public void setGatekeeperServiceURI(String gatekeeperServiceURI) {
+ this.gatekeeperServiceURI = gatekeeperServiceURI;
+ }
+
+ public String getAuthenticationInfo() {
+ return authenticationInfo;
+ }
+
+ public void setAuthenticationInfo(String authenticationInfo) {
+ this.authenticationInfo = authenticationInfo;
+ }
+
+
+ public Boolean isSecure() {
+ return secure;
+ }
+
+ public void setSecure(Boolean secure) {
+ this.secure = secure;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) {
+ return true;
+ }
+ if (!(o instanceof ArrowheadCloud)) {
+ return false;
+ }
+ ArrowheadCloud that = (ArrowheadCloud) o;
+ return Objects.equals(operator, that.operator) && Objects.equals(cloudName, that.cloudName) && Objects.equals(address, that.address) && Objects
+ .equals(port, that.port) && Objects.equals(gatekeeperServiceURI, that.gatekeeperServiceURI) && Objects.equals(secure, that.secure);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(operator, cloudName, address, port, gatekeeperServiceURI, secure);
+ }
+
+ @Override
+ public String toString() {
+ return MoreObjects.toStringHelper(this).add("operator", operator).add("cloudName", cloudName).add("address", address).add("port", port)
+ .add("gatekeeperServiceURI", gatekeeperServiceURI).add("secure", secure).toString();
+ }
+
+ public void partialUpdate(ArrowheadCloud other) {
+ this.operator = other.getOperator() != null ? other.getOperator() : this.operator;
+ this.cloudName = other.getCloudName() != null ? other.getCloudName() : this.cloudName;
+ this.address = other.getAddress() != null ? other.getAddress() : this.address;
+ this.port = other.getPort() != null ? other.getPort() : this.port;
+ this.gatekeeperServiceURI = other.getGatekeeperServiceURI() != null ? other.getGatekeeperServiceURI() : this.gatekeeperServiceURI;
+ this.authenticationInfo = other.getAuthenticationInfo() != null ? other.getAuthenticationInfo() : this.authenticationInfo;
+ this.secure = other.isSecure() != null ? other.isSecure() : this.secure;
+ }
+}
\ No newline at end of file
diff --git a/core-common/src/main/java/eu/arrowhead/common/database/ArrowheadDevice.java b/core-common/src/main/java/eu/arrowhead/common/database/ArrowheadDevice.java
new file mode 100644
index 00000000..ec8a9928
--- /dev/null
+++ b/core-common/src/main/java/eu/arrowhead/common/database/ArrowheadDevice.java
@@ -0,0 +1,80 @@
+/*
+ * This work is part of the Productive 4.0 innovation project, which receives grants from the
+ * European Commissions H2020 research and innovation programme, ECSEL Joint Undertaking
+ * (project no. 737459), the free state of Saxony, the German Federal Ministry of Education and
+ * national funding authorities from involved countries.
+ */
+
+package eu.arrowhead.common.database;
+
+import com.google.common.base.MoreObjects;
+import java.util.Objects;
+import javax.persistence.Column;
+import javax.persistence.Entity;
+import javax.persistence.GeneratedValue;
+import javax.persistence.GenerationType;
+import javax.persistence.Id;
+import javax.persistence.Table;
+import javax.validation.constraints.Size;
+import org.hibernate.annotations.GenericGenerator;
+import org.hibernate.validator.constraints.NotBlank;
+
+@Entity
+@Table(name = "arrowhead_device")
+public class ArrowheadDevice {
+
+ @Id
+ @GenericGenerator(name = "table_generator", strategy = "org.hibernate.id.enhanced.TableGenerator")
+ @GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "table_generator")
+ private Long id;
+
+ @NotBlank
+ @Column(name = "device_name")
+ @Size(max = 255, message = "System name must be 255 character at max")
+ private String deviceName;
+
+ public ArrowheadDevice() {
+ }
+
+ public ArrowheadDevice(String deviceName) {
+ this.deviceName = deviceName;
+ }
+
+ public Long getId() {
+ return id;
+ }
+
+ public void setId(Long id) {
+ this.id = id;
+ }
+
+ public String getDeviceName() {
+ return deviceName;
+ }
+
+ public void setDeviceName(String deviceName) {
+ this.deviceName = deviceName;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) {
+ return true;
+ }
+ if (!(o instanceof ArrowheadDevice)) {
+ return false;
+ }
+ ArrowheadDevice that = (ArrowheadDevice) o;
+ return Objects.equals(deviceName, that.deviceName);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(deviceName);
+ }
+
+ @Override
+ public String toString() {
+ return MoreObjects.toStringHelper(this).add("deviceName", deviceName).toString();
+ }
+}
diff --git a/core-common/src/main/java/eu/arrowhead/common/database/ArrowheadService.java b/core-common/src/main/java/eu/arrowhead/common/database/ArrowheadService.java
new file mode 100644
index 00000000..86421543
--- /dev/null
+++ b/core-common/src/main/java/eu/arrowhead/common/database/ArrowheadService.java
@@ -0,0 +1,182 @@
+/*
+ * This work is part of the Productive 4.0 innovation project, which receives grants from the
+ * European Commissions H2020 research and innovation programme, ECSEL Joint Undertaking
+ * (project no. 737459), the free state of Saxony, the German Federal Ministry of Education and
+ * national funding authorities from involved countries.
+ */
+
+package eu.arrowhead.common.database;
+
+import com.fasterxml.jackson.annotation.JsonInclude;
+import com.fasterxml.jackson.annotation.JsonInclude.Include;
+import com.fasterxml.jackson.annotation.JsonSetter;
+import com.google.common.base.MoreObjects;
+import eu.arrowhead.common.Utility;
+import eu.arrowhead.common.exception.BadPayloadException;
+import eu.arrowhead.common.json.support.ArrowheadServiceSupport;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Objects;
+import java.util.Set;
+import javax.persistence.CollectionTable;
+import javax.persistence.Column;
+import javax.persistence.ElementCollection;
+import javax.persistence.Entity;
+import javax.persistence.FetchType;
+import javax.persistence.GeneratedValue;
+import javax.persistence.GenerationType;
+import javax.persistence.Id;
+import javax.persistence.JoinColumn;
+import javax.persistence.Table;
+import javax.persistence.Transient;
+import javax.persistence.UniqueConstraint;
+import javax.validation.constraints.Size;
+import org.hibernate.annotations.GenericGenerator;
+import org.hibernate.validator.constraints.NotBlank;
+
+/**
+ * Representation of a service within Arrowhead.
+ *
+ * @author uzoltan
+ * @since 4.2
+ */
+@Entity
+@Table(name = "arrowhead_service", uniqueConstraints = {@UniqueConstraint(columnNames = {"service_definition"})})
+public class ArrowheadService {
+
+ @Id
+ @GenericGenerator(name = "table_generator", strategy = "org.hibernate.id.enhanced.TableGenerator")
+ @GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "table_generator")
+ private Long id;
+
+ @NotBlank
+ @Size(max = 255, message = "Service serviceDefinition must be 255 character at max")
+ @Column(name = "service_definition")
+ private String serviceDefinition;
+
+ @Size(max = 100, message = "Service can only have 100 interfaces at max")
+ @ElementCollection(fetch = FetchType.EAGER)
+ @CollectionTable(name = "arrowhead_service_interfaces", joinColumns = @JoinColumn(name = "arrowhead_service_id"))
+ private Set interfaces = new HashSet<>();
+
+ @Transient
+ @JsonInclude(Include.NON_EMPTY)
+ @Size(max = 100, message = "Service can only have 100 serviceMetadata key-value pairs at max")
+ private Map serviceMetadata = new HashMap<>();
+
+ public ArrowheadService() {
+ }
+
+ /**
+ * Constructor with all the fields of the ArrowheadService class.
+ *
+ * @param serviceDefinition A descriptive name for the service
+ * @param interfaces The set of interfaces that can be used to consume this service (helps interoperability between
+ * ArrowheadSystems). Concrete meaning of what is an interface is service specific (e.g. JSON, I2C)
+ * @param serviceMetadata Arbitrary additional serviceMetadata belonging to the service, stored as key-value pairs.
+ */
+ public ArrowheadService(String serviceDefinition, Set interfaces, Map serviceMetadata) {
+ this.serviceDefinition = serviceDefinition;
+ this.interfaces = interfaces;
+ this.serviceMetadata = serviceMetadata;
+ }
+
+ public ArrowheadService(ArrowheadServiceSupport service) {
+ this.serviceDefinition = service.getServiceGroup() + "_" + service.getServiceDefinition();
+ this.interfaces = new HashSet<>(service.getInterfaces());
+ this.serviceMetadata = service.getServiceMetadata();
+ }
+
+ public Long getId() {
+ return id;
+ }
+
+ public void setId(Long id) {
+ this.id = id;
+ }
+
+ public String getServiceDefinition() {
+ return serviceDefinition;
+ }
+
+ public void setServiceDefinition(String serviceDefinition) {
+ this.serviceDefinition = serviceDefinition;
+ }
+
+ public Set getInterfaces() {
+ return interfaces;
+ }
+
+ @JsonSetter
+ public void setInterfaces(Set interfaces) {
+ for (String serviceInterface : interfaces) {
+ if (serviceInterface == null || serviceInterface.trim().isEmpty()) {
+ throw new BadPayloadException("ArrowheadService interface can not be blank!");
+ }
+ }
+ this.interfaces = interfaces;
+ }
+
+ public Map getServiceMetadata() {
+ return serviceMetadata;
+ }
+
+ @JsonSetter
+ public void setServiceMetadata(Map serviceMetadata) {
+ for (Map.Entry entry : serviceMetadata.entrySet()) {
+ String key = entry.getKey();
+ if (key == null || key.trim().isEmpty()) {
+ throw new BadPayloadException("ArrowheadService metadata key can not be blank!");
+ }
+ String value = entry.getValue();
+ if (value == null || value.trim().isEmpty()) {
+ throw new BadPayloadException("ArrowheadService metadata value can not be blank!");
+ }
+ }
+ this.serviceMetadata = serviceMetadata;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) {
+ return true;
+ }
+ if (!(o instanceof ArrowheadService)) {
+ return false;
+ }
+ ArrowheadService that = (ArrowheadService) o;
+
+ if (!Objects.equals(serviceDefinition, that.serviceDefinition)) {
+ return false;
+ }
+
+ //2 services can be equal if they have at least 1 common interface
+ if (interfaces == null || that.interfaces == null) {
+ return true;
+ } else {
+ Set intersection = new HashSet<>(interfaces);
+ intersection.retainAll(that.interfaces);
+ return !intersection.isEmpty();
+ }
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(serviceDefinition);
+ }
+
+ @Override
+ public String toString() {
+ return MoreObjects.toStringHelper(this).add("serviceDefinition", serviceDefinition).toString();
+ }
+
+ @SuppressWarnings("unchecked")
+ public void partialUpdate(ArrowheadService other) {
+ this.serviceDefinition = other.getServiceDefinition() != null ? other.getServiceDefinition() : this.serviceDefinition;
+ //Making deep copies of the collections with the help of JSON (de)serialization
+ this.interfaces = other.getInterfaces().isEmpty() ? this.interfaces : Utility
+ .fromJson(Utility.toPrettyJson(null, other.getInterfaces()), Set.class);
+ this.serviceMetadata = other.getServiceMetadata().isEmpty() ? this.serviceMetadata : Utility.fromJson(Utility.toPrettyJson(null, other.getServiceMetadata()), Map.class);
+ }
+}
\ No newline at end of file
diff --git a/core-common/src/main/java/eu/arrowhead/common/database/ArrowheadSystem.java b/core-common/src/main/java/eu/arrowhead/common/database/ArrowheadSystem.java
new file mode 100644
index 00000000..78441b02
--- /dev/null
+++ b/core-common/src/main/java/eu/arrowhead/common/database/ArrowheadSystem.java
@@ -0,0 +1,171 @@
+/*
+ * This work is part of the Productive 4.0 innovation project, which receives grants from the
+ * European Commissions H2020 research and innovation programme, ECSEL Joint Undertaking
+ * (project no. 737459), the free state of Saxony, the German Federal Ministry of Education and
+ * national funding authorities from involved countries.
+ */
+
+package eu.arrowhead.common.database;
+
+import eu.arrowhead.common.json.support.ArrowheadSystemSupport;
+import java.util.Objects;
+import javax.persistence.Column;
+import javax.persistence.Entity;
+import javax.persistence.GeneratedValue;
+import javax.persistence.GenerationType;
+import javax.persistence.Id;
+import javax.persistence.Table;
+import javax.persistence.UniqueConstraint;
+import javax.validation.constraints.Max;
+import javax.validation.constraints.Min;
+import javax.validation.constraints.NotNull;
+import javax.validation.constraints.Pattern;
+import javax.validation.constraints.Size;
+import org.hibernate.annotations.GenericGenerator;
+import org.hibernate.validator.constraints.NotBlank;
+
+@Entity
+@Table(name = "arrowhead_system", uniqueConstraints = {@UniqueConstraint(columnNames = {"system_name", "address", "port"})})
+public class ArrowheadSystem {
+
+ @Id
+ @GenericGenerator(name = "table_generator", strategy = "org.hibernate.id.enhanced.TableGenerator")
+ @GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "table_generator")
+ private Long id;
+
+ @NotBlank
+ @Size(max = 255, message = "System name must be 255 character at max")
+ @Pattern(regexp = "[\\p{L}0-9-_:]+", message =
+ "System name can only contain alphanumerical characters and some special characters (dash, "
+ + "underscore and colon)")
+ @Column(name = "system_name")
+ private String systemName;
+
+ @NotBlank
+ @Size(min = 3, max = 255, message = "Address must be between 3 and 255 characters")
+ private String address;
+
+ @NotNull
+ @Min(value = 1, message = "Port can not be less than 1")
+ @Max(value = 65535, message = "Port can not be greater than 65535")
+ private Integer port;
+
+ @Column(name = "authentication_info")
+ @Size(max = 2047, message = "Authentication information must be 2047 character at max")
+ private String authenticationInfo;
+
+ public ArrowheadSystem() {
+ }
+
+ public ArrowheadSystem(String systemName, String address, Integer port, String authenticationInfo) {
+ this.systemName = systemName;
+ this.address = address;
+ this.port = port;
+ this.authenticationInfo = authenticationInfo;
+ }
+
+ public ArrowheadSystem(String json) {
+ String[] fields = json.split(",");
+ this.systemName = fields[0].equals("null") ? null : fields[0];
+ this.address = fields[1].equals("null") ? null : fields[1];
+ this.port = Integer.valueOf(fields[2]);
+
+ if (fields.length == 4) {
+ this.authenticationInfo = fields[3].equals("null") ? null : fields[3];
+ }
+ }
+
+ public ArrowheadSystem(ArrowheadSystemSupport system) {
+ this.systemName = system.getSystemGroup() + "_" + system.getSystemName();
+ this.address = system.getAddress();
+ this.port = system.getPort();
+ this.authenticationInfo = system.getAuthenticationInfo();
+ }
+
+ @SuppressWarnings("CopyConstructorMissesField")
+ public ArrowheadSystem(ArrowheadSystem system) {
+ this.systemName = system.systemName;
+ this.address = system.address;
+ this.port = system.port;
+ this.authenticationInfo = system.authenticationInfo;
+ }
+
+ public Long getId() {
+ return id;
+ }
+
+ public void setId(Long id) {
+ this.id = id;
+ }
+
+ public String getSystemName() {
+ return systemName;
+ }
+
+ public void setSystemName(String systemName) {
+ this.systemName = systemName;
+ }
+
+ public String getAddress() {
+ return address;
+ }
+
+ public void setAddress(String address) {
+ this.address = address;
+ }
+
+ public Integer getPort() {
+ return port;
+ }
+
+ public void setPort(Integer port) {
+ this.port = port;
+ }
+
+ public String getAuthenticationInfo() {
+ return authenticationInfo;
+ }
+
+ public void setAuthenticationInfo(String authenticationInfo) {
+ this.authenticationInfo = authenticationInfo;
+ }
+
+ public String toArrowheadCommonName(String operator, String cloudName) {
+ if (systemName.contains(".") || operator.contains(".") || cloudName.contains(".")) {
+ throw new IllegalArgumentException("The string fields can not contain dots!");
+ }
+ //throws NPE if any of the fields are null
+ return systemName.concat(".").concat(cloudName).concat(".").concat(operator).concat(".").concat("arrowhead.eu");
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) {
+ return true;
+ }
+ if (!(o instanceof ArrowheadSystem)) {
+ return false;
+ }
+ ArrowheadSystem that = (ArrowheadSystem) o;
+ return Objects.equals(systemName, that.systemName) && Objects.equals(address, that.address) && Objects.equals(port, that.port);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(systemName, address, port);
+ }
+
+ //NOTE ArrowheadSystemKeyDeserializer relies on this implementation, do not change it without changing the (String json) constructor
+ @Override
+ public String toString() {
+ return systemName + "," + address + "," + port + "," + authenticationInfo;
+ }
+
+ public void partialUpdate(ArrowheadSystem other) {
+ this.systemName = other.getSystemName() != null ? other.getSystemName() : this.systemName;
+ this.address = other.getAddress() != null ? other.getAddress() : this.address;
+ this.port = other.getPort() != null ? other.getPort() : this.port;
+ this.authenticationInfo = other.getAuthenticationInfo() != null ? other.getAuthenticationInfo() : this.authenticationInfo;
+ }
+
+}
diff --git a/core-common/src/main/java/eu/arrowhead/common/database/Broker.java b/core-common/src/main/java/eu/arrowhead/common/database/Broker.java
new file mode 100644
index 00000000..c96359f4
--- /dev/null
+++ b/core-common/src/main/java/eu/arrowhead/common/database/Broker.java
@@ -0,0 +1,107 @@
+/*
+ * This work is part of the Productive 4.0 innovation project, which receives grants from the
+ * European Commissions H2020 research and innovation programme, ECSEL Joint Undertaking
+ * (project no. 737459), the free state of Saxony, the German Federal Ministry of Education and
+ * national funding authorities from involved countries.
+ */
+
+package eu.arrowhead.common.database;
+
+import com.google.common.base.MoreObjects;
+import java.util.Objects;
+import javax.persistence.Column;
+import javax.persistence.Entity;
+import javax.persistence.GeneratedValue;
+import javax.persistence.GenerationType;
+import javax.persistence.Id;
+import javax.persistence.Table;
+import javax.validation.constraints.Max;
+import javax.validation.constraints.Min;
+import javax.validation.constraints.NotNull;
+import javax.validation.constraints.Size;
+import org.hibernate.annotations.GenericGenerator;
+import org.hibernate.annotations.Type;
+import org.hibernate.validator.constraints.NotBlank;
+
+@Entity
+@Table(name = "broker")
+public class Broker {
+
+ @Id
+ @GenericGenerator(name = "table_generator", strategy = "org.hibernate.id.enhanced.TableGenerator")
+ @GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "table_generator")
+ private Long id;
+
+ @NotBlank
+ @Size(min = 3, max = 255, message = "Address must be between 3 and 255 characters")
+ private String address;
+
+ @NotNull
+ @Min(value = 1, message = "Port can not be less than 1")
+ @Max(value = 65535, message = "Port can not be greater than 65535")
+ private Integer port;
+
+ @Column(name = "is_secure")
+ @Type(type = "yes_no")
+ private Boolean secure = false;
+
+ public Broker() {
+ }
+
+ public Broker(String address, Integer port, Boolean secure) {
+ this.address = address;
+ this.port = port;
+ this.secure = secure;
+ }
+
+ public Long getId() {
+ return id;
+ }
+
+ public String getAddress() {
+ return address;
+ }
+
+ public void setAddress(String address) {
+ this.address = address;
+ }
+
+ public Integer getPort() {
+ return port;
+ }
+
+ public void setPort(Integer port) {
+ this.port = port;
+ }
+
+
+ public Boolean isSecure() {
+ return secure;
+ }
+
+ public void setSecure(Boolean secure) {
+ this.secure = secure;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) {
+ return true;
+ }
+ if (!(o instanceof Broker)) {
+ return false;
+ }
+ Broker broker = (Broker) o;
+ return Objects.equals(address, broker.address) && Objects.equals(port, broker.port) && Objects.equals(secure, broker.secure);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(address, port, secure);
+ }
+
+ @Override
+ public String toString() {
+ return MoreObjects.toStringHelper(this).add("address", address).add("port", port).add("secure", secure).toString();
+ }
+}
diff --git a/core-common/src/main/java/eu/arrowhead/common/database/DeviceRegistryEntry.java b/core-common/src/main/java/eu/arrowhead/common/database/DeviceRegistryEntry.java
new file mode 100644
index 00000000..774bab8f
--- /dev/null
+++ b/core-common/src/main/java/eu/arrowhead/common/database/DeviceRegistryEntry.java
@@ -0,0 +1,139 @@
+/*
+ * This work is part of the Productive 4.0 innovation project, which receives grants from the
+ * European Commissions H2020 research and innovation programme, ECSEL Joint Undertaking
+ * (project no. 737459), the free state of Saxony, the German Federal Ministry of Education and
+ * national funding authorities from involved countries.
+ */
+
+package eu.arrowhead.common.database;
+
+import eu.arrowhead.common.json.constraint.LDTInFuture;
+import java.time.LocalDateTime;
+import java.util.Objects;
+import javax.persistence.CascadeType;
+import javax.persistence.Column;
+import javax.persistence.Entity;
+import javax.persistence.FetchType;
+import javax.persistence.GeneratedValue;
+import javax.persistence.GenerationType;
+import javax.persistence.Id;
+import javax.persistence.JoinColumn;
+import javax.persistence.ManyToOne;
+import javax.persistence.Table;
+import javax.persistence.UniqueConstraint;
+import javax.validation.Valid;
+import javax.validation.constraints.NotNull;
+import javax.validation.constraints.Size;
+import org.hibernate.annotations.GenericGenerator;
+import org.hibernate.annotations.OnDelete;
+import org.hibernate.annotations.OnDeleteAction;
+
+@Entity
+@Table(name = "device_registry", uniqueConstraints = {@UniqueConstraint(columnNames = {"arrowhead_device_id"})})
+public class DeviceRegistryEntry {
+
+ @Id
+ @GenericGenerator(name = "table_generator", strategy = "org.hibernate.id.enhanced.TableGenerator")
+ @GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "table_generator")
+ private Long id;
+
+ @Valid
+ @NotNull(message = "Provided ArrowheadDevice cannot be null")
+ @JoinColumn(name = "arrowhead_device_id")
+ @ManyToOne(fetch = FetchType.EAGER, cascade = {CascadeType.PERSIST, CascadeType.MERGE})
+ @OnDelete(action = OnDeleteAction.CASCADE)
+ private ArrowheadDevice providedDevice;
+
+ @Column(name = "mac_address")
+ @Size(max = 255, message = "macAddress must be 255 character at max")
+ private String macAddress;
+
+ @Column(name = "end_of_validity")
+ @LDTInFuture(message = "End of validity date cannot be in the past")
+ private LocalDateTime endOfValidity;
+
+ public DeviceRegistryEntry() {
+ super();
+ }
+
+ public DeviceRegistryEntry(final Long id, @Valid @NotNull(message = "Provided ArrowheadDevice cannot be null") final ArrowheadDevice providedDevice,
+ @Size(max = 255, message = "macAddress must be 255 character at max") final String macAddress,
+ @LDTInFuture(message = "End of validity date cannot be in the past") final LocalDateTime endOfValidity) {
+ super();
+ this.id = id;
+ this.providedDevice = providedDevice;
+ this.macAddress = macAddress;
+ this.endOfValidity = endOfValidity;
+ }
+
+ public Long getId() {
+ return id;
+ }
+
+ public void setId(Long id) {
+ this.id = id;
+ }
+
+ public ArrowheadDevice getProvidedDevice() {
+ return providedDevice;
+ }
+
+ public void setProvidedDevice(ArrowheadDevice providedDevice) {
+ this.providedDevice = providedDevice;
+ }
+
+ public String getMacAddress() {
+ return macAddress;
+ }
+
+ public void setMacAddress(String macAddress) {
+ this.macAddress = macAddress;
+ }
+
+ public LocalDateTime getEndOfValidity() {
+ return endOfValidity;
+ }
+
+ public void setEndOfValidity(LocalDateTime endOfValidity) {
+ this.endOfValidity = endOfValidity;
+ }
+
+ protected void append(final StringBuilder builder) {
+ builder.append("id=").append(id);
+ builder.append(", providedDevice=").append(providedDevice);
+ builder.append(", macAddress=").append(macAddress);
+ builder.append(", endOfValidity=").append(endOfValidity);
+ }
+
+ @Override
+ public String toString() {
+ StringBuilder builder = new StringBuilder();
+ builder.append(getClass().getSimpleName());
+ builder.append(" [");
+ append(builder);
+ builder.append("]");
+ return builder.toString();
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (this == obj) {
+ return true;
+ }
+ if (obj == null) {
+ return false;
+ }
+ if (getClass() != obj.getClass()) {
+ return false;
+ }
+ DeviceRegistryEntry other = (DeviceRegistryEntry) obj;
+
+ return Objects.equals(this.providedDevice, other.providedDevice) && Objects.equals(this.macAddress, other.macAddress) && Objects
+ .equals(this.endOfValidity, other.endOfValidity);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(providedDevice, macAddress, endOfValidity);
+ }
+}
diff --git a/core-common/src/main/java/eu/arrowhead/common/database/EventFilter.java b/core-common/src/main/java/eu/arrowhead/common/database/EventFilter.java
new file mode 100644
index 00000000..f24ff2b4
--- /dev/null
+++ b/core-common/src/main/java/eu/arrowhead/common/database/EventFilter.java
@@ -0,0 +1,223 @@
+/*
+ * This work is part of the Productive 4.0 innovation project, which receives grants from the
+ * European Commissions H2020 research and innovation programme, ECSEL Joint Undertaking
+ * (project no. 737459), the free state of Saxony, the German Federal Ministry of Education and
+ * national funding authorities from involved countries.
+ */
+
+package eu.arrowhead.common.database;
+
+import com.fasterxml.jackson.annotation.JsonSetter;
+import com.google.common.base.MoreObjects;
+import eu.arrowhead.common.exception.BadPayloadException;
+import eu.arrowhead.common.json.constraint.ZDTInFuture;
+import java.time.ZonedDateTime;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Objects;
+import java.util.Set;
+import javax.persistence.CascadeType;
+import javax.persistence.CollectionTable;
+import javax.persistence.Column;
+import javax.persistence.ElementCollection;
+import javax.persistence.Entity;
+import javax.persistence.FetchType;
+import javax.persistence.GeneratedValue;
+import javax.persistence.GenerationType;
+import javax.persistence.Id;
+import javax.persistence.JoinColumn;
+import javax.persistence.ManyToOne;
+import javax.persistence.MapKeyColumn;
+import javax.persistence.Table;
+import javax.persistence.UniqueConstraint;
+import javax.validation.Valid;
+import javax.validation.constraints.NotNull;
+import javax.validation.constraints.Size;
+import org.hibernate.annotations.GenericGenerator;
+import org.hibernate.annotations.OnDelete;
+import org.hibernate.annotations.OnDeleteAction;
+import org.hibernate.annotations.Type;
+import org.hibernate.validator.constraints.NotBlank;
+
+@Entity
+@Table(name = "event_filter", uniqueConstraints = {
+ @UniqueConstraint(columnNames = {"event_type", "consumer_system_id"})})
+public class EventFilter {
+
+ @Id
+ @GenericGenerator(name = "table_generator", strategy = "org.hibernate.id.enhanced.TableGenerator")
+ @GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "table_generator")
+ private Long id;
+
+ @NotBlank
+ @Column(name = "event_type")
+ @Size(max = 255, message = "Event type must be 255 character at max")
+ private String eventType;
+
+ @Valid
+ @NotNull(message = "Consumer ArrowheadSystem cannot be null")
+ @JoinColumn(name = "consumer_system_id")
+ @ManyToOne(fetch = FetchType.EAGER, cascade = {CascadeType.PERSIST, CascadeType.MERGE})
+ @OnDelete(action = OnDeleteAction.CASCADE)
+ private ArrowheadSystem consumer;
+
+ @Valid
+ @Size(max = 100, message = "Event filter can only have 100 sources at max")
+ @ElementCollection(fetch = FetchType.EAGER)
+ @CollectionTable(name = "event_filter_sources_list", joinColumns = @JoinColumn(name = "filter_id"))
+ private Set sources = new HashSet<>();
+
+ @Column(name = "start_date")
+ private ZonedDateTime startDate;
+
+ @Column(name = "end_date")
+ @ZDTInFuture(message = "Filter end date must be in the future")
+ private ZonedDateTime endDate;
+
+ @ElementCollection(fetch = FetchType.EAGER)
+ @MapKeyColumn(name = "metadata_key")
+ @Column(name = "metadata_value", length = 2047)
+ @CollectionTable(name = "event_filter_metadata", joinColumns = @JoinColumn(name = "filter_id"))
+ private Map filterMetadata = new HashMap<>();
+
+ @Column(name = "notify_uri")
+ private String notifyUri;
+
+ //TODO provide a REST interface to easily switch this
+ @Column(name = "match_metadata")
+ @Type(type = "yes_no")
+ private Boolean matchMetadata = false;
+
+ public EventFilter() {
+ }
+
+ public EventFilter(String eventType, ArrowheadSystem consumer, Set sources, ZonedDateTime startDate,
+ ZonedDateTime endDate, Map filterMetadata, String notifyUri,
+ boolean matchMetadata) {
+ this.eventType = eventType;
+ this.consumer = consumer;
+ this.sources = sources;
+ this.startDate = startDate;
+ this.endDate = endDate;
+ this.filterMetadata = filterMetadata;
+ this.notifyUri = notifyUri;
+ this.matchMetadata = matchMetadata;
+ }
+
+ public Long getId() {
+ return id;
+ }
+
+ public void setId(Long id) {
+ this.id = id;
+ }
+
+ public String getEventType() {
+ return eventType;
+ }
+
+ public void setEventType(String eventType) {
+ this.eventType = eventType;
+ }
+
+ public ArrowheadSystem getConsumer() {
+ return consumer;
+ }
+
+ public void setConsumer(ArrowheadSystem consumer) {
+ this.consumer = consumer;
+ }
+
+ public Set getSources() {
+ return sources;
+ }
+
+ public void setSources(Set sources) {
+ this.sources = sources;
+ }
+
+ public ZonedDateTime getStartDate() {
+ return startDate;
+ }
+
+ public void setStartDate(ZonedDateTime startDate) {
+ this.startDate = startDate;
+ }
+
+ public ZonedDateTime getEndDate() {
+ return endDate;
+ }
+
+ public void setEndDate(ZonedDateTime endDate) {
+ this.endDate = endDate;
+ }
+
+ public Map