diff --git a/.jupyter/api_authentication/__init__.py b/.jupyter/api_authentication/__init__.py index e48c6068bdc61ab1bfab47ef30aee1e39ac97cc5..2c48db22770b0ebd34d7872a2dc7cd1416837ec2 100644 --- a/.jupyter/api_authentication/__init__.py +++ b/.jupyter/api_authentication/__init__.py @@ -28,6 +28,7 @@ from api_authentication.models.concept_dto import ConceptDto from api_authentication.models.container_dto import ContainerDto from api_authentication.models.database_dto import DatabaseDto from api_authentication.models.granted_authority_dto import GrantedAuthorityDto +from api_authentication.models.image_brief_dto import ImageBriefDto from api_authentication.models.image_date_dto import ImageDateDto from api_authentication.models.image_dto import ImageDto from api_authentication.models.image_env_item_dto import ImageEnvItemDto diff --git a/.jupyter/api_authentication/models/__init__.py b/.jupyter/api_authentication/models/__init__.py index d21bff01651519a7787b82db2eee3b2a9fbc3b8e..11e22a17bacc695efc8b6692fac98724d7c4c9a2 100644 --- a/.jupyter/api_authentication/models/__init__.py +++ b/.jupyter/api_authentication/models/__init__.py @@ -20,6 +20,7 @@ from api_authentication.models.concept_dto import ConceptDto from api_authentication.models.container_dto import ContainerDto from api_authentication.models.database_dto import DatabaseDto from api_authentication.models.granted_authority_dto import GrantedAuthorityDto +from api_authentication.models.image_brief_dto import ImageBriefDto from api_authentication.models.image_date_dto import ImageDateDto from api_authentication.models.image_dto import ImageDto from api_authentication.models.image_env_item_dto import ImageEnvItemDto diff --git a/.jupyter/api_authentication/models/column_dto.py b/.jupyter/api_authentication/models/column_dto.py index ed8a04962d91b77b04e92a44baf6688558537106..04d7b1abfcfd9e184e3f24cc68e72b0d725e0beb 100644 --- a/.jupyter/api_authentication/models/column_dto.py +++ b/.jupyter/api_authentication/models/column_dto.py @@ -309,7 +309,7 @@ class ColumnDto(object): """ if column_type is None: raise ValueError("Invalid value for `column_type`, must not be `None`") # noqa: E501 - allowed_values = ["ColumnTypeDto.ENUM", "ColumnTypeDto.NUMBER", "ColumnTypeDto.DECIMAL", "ColumnTypeDto.STRING", "ColumnTypeDto.TEXT", "ColumnTypeDto.BOOLEAN", "ColumnTypeDto.DATE", "ColumnTypeDto.TIMESTAMP", "ColumnTypeDto.BLOB"] # noqa: E501 + allowed_values = ["enum", "number", "decimal", "string", "text", "boolean", "date", "timestamp", "blob"] # noqa: E501 if column_type not in allowed_values: raise ValueError( "Invalid value for `column_type` ({0}), must be one of {1}" # noqa: E501 diff --git a/.jupyter/api_authentication/models/container_dto.py b/.jupyter/api_authentication/models/container_dto.py index 0b1f4876692bacc7f6d1362819d8491722fb8d1b..71a769288040f3fb38230ef7e08120681a04c8c2 100644 --- a/.jupyter/api_authentication/models/container_dto.py +++ b/.jupyter/api_authentication/models/container_dto.py @@ -33,7 +33,7 @@ class ContainerDto(object): 'name': 'str', 'state': 'str', 'databases': 'list[DatabaseDto]', - 'image': 'ImageDto', + 'image': 'ImageBriefDto', 'port': 'int', 'created': 'datetime', 'internal_name': 'str', @@ -210,7 +210,7 @@ class ContainerDto(object): :return: The image of this ContainerDto. # noqa: E501 - :rtype: ImageDto + :rtype: ImageBriefDto """ return self._image @@ -220,7 +220,7 @@ class ContainerDto(object): :param image: The image of this ContainerDto. # noqa: E501 - :type: ImageDto + :type: ImageBriefDto """ self._image = image diff --git a/.jupyter/api_container/__init__.py b/.jupyter/api_container/__init__.py index de472c538ba49ce2197523cf8d2305661fb5d4f4..d659282125f720eb4fac3822eca06b9c9aed2145 100644 --- a/.jupyter/api_container/__init__.py +++ b/.jupyter/api_container/__init__.py @@ -38,4 +38,5 @@ from api_container.models.image_dto import ImageDto from api_container.models.image_env_item_dto import ImageEnvItemDto from api_container.models.license_dto import LicenseDto from api_container.models.table_dto import TableDto +from api_container.models.user_brief_dto import UserBriefDto from api_container.models.user_dto import UserDto diff --git a/.jupyter/api_container/models/__init__.py b/.jupyter/api_container/models/__init__.py index 427267c595c3729ead76cfed7767435d879c58d8..9f7e2ea74892493aa95f3dbda75793dc6216d075 100644 --- a/.jupyter/api_container/models/__init__.py +++ b/.jupyter/api_container/models/__init__.py @@ -31,4 +31,5 @@ from api_container.models.image_dto import ImageDto from api_container.models.image_env_item_dto import ImageEnvItemDto from api_container.models.license_dto import LicenseDto from api_container.models.table_dto import TableDto +from api_container.models.user_brief_dto import UserBriefDto from api_container.models.user_dto import UserDto diff --git a/.jupyter/api_container/models/column_dto.py b/.jupyter/api_container/models/column_dto.py index 14304875e64aee7c9f16f6709aefcdba0c376def..96e26927200e62d98f54e2ea8f231a0262f6955c 100644 --- a/.jupyter/api_container/models/column_dto.py +++ b/.jupyter/api_container/models/column_dto.py @@ -309,7 +309,7 @@ class ColumnDto(object): """ if column_type is None: raise ValueError("Invalid value for `column_type`, must not be `None`") # noqa: E501 - allowed_values = ["ColumnTypeDto.ENUM", "ColumnTypeDto.NUMBER", "ColumnTypeDto.DECIMAL", "ColumnTypeDto.STRING", "ColumnTypeDto.TEXT", "ColumnTypeDto.BOOLEAN", "ColumnTypeDto.DATE", "ColumnTypeDto.TIMESTAMP", "ColumnTypeDto.BLOB"] # noqa: E501 + allowed_values = ["enum", "number", "decimal", "string", "text", "boolean", "date", "timestamp", "blob"] # noqa: E501 if column_type not in allowed_values: raise ValueError( "Invalid value for `column_type` ({0}), must be one of {1}" # noqa: E501 diff --git a/.jupyter/api_container/models/container_brief_dto.py b/.jupyter/api_container/models/container_brief_dto.py index 09be8434558fb80640e190d4d967c1e7c7129a37..658135d4891a777ee6738fc40c0f529de057cb4d 100644 --- a/.jupyter/api_container/models/container_brief_dto.py +++ b/.jupyter/api_container/models/container_brief_dto.py @@ -31,7 +31,7 @@ class ContainerBriefDto(object): 'id': 'int', 'hash': 'str', 'name': 'str', - 'creator': 'UserDto', + 'creator': 'UserBriefDto', 'created': 'datetime', 'internal_name': 'str', 'is_public': 'bool' @@ -143,7 +143,7 @@ class ContainerBriefDto(object): :return: The creator of this ContainerBriefDto. # noqa: E501 - :rtype: UserDto + :rtype: UserBriefDto """ return self._creator @@ -153,7 +153,7 @@ class ContainerBriefDto(object): :param creator: The creator of this ContainerBriefDto. # noqa: E501 - :type: UserDto + :type: UserBriefDto """ self._creator = creator diff --git a/.jupyter/api_container/models/container_dto.py b/.jupyter/api_container/models/container_dto.py index 4cc2f1bf2e3336e2e5bda2be250a361fb0aaddec..b0c0216b6a882d95b2a8b9524e3ac2f21fea122f 100644 --- a/.jupyter/api_container/models/container_dto.py +++ b/.jupyter/api_container/models/container_dto.py @@ -33,7 +33,7 @@ class ContainerDto(object): 'name': 'str', 'state': 'str', 'databases': 'list[DatabaseDto]', - 'image': 'ImageDto', + 'image': 'ImageBriefDto', 'port': 'int', 'created': 'datetime', 'internal_name': 'str', @@ -210,7 +210,7 @@ class ContainerDto(object): :return: The image of this ContainerDto. # noqa: E501 - :rtype: ImageDto + :rtype: ImageBriefDto """ return self._image @@ -220,7 +220,7 @@ class ContainerDto(object): :param image: The image of this ContainerDto. # noqa: E501 - :type: ImageDto + :type: ImageBriefDto """ self._image = image diff --git a/.jupyter/api_database/__init__.py b/.jupyter/api_database/__init__.py index 55e5291ccf85b5a3911d51e5f467c21b36dbb7c5..8c1403ffcfb84a6edfa6d3f031569274f0a79d5f 100644 --- a/.jupyter/api_database/__init__.py +++ b/.jupyter/api_database/__init__.py @@ -31,9 +31,11 @@ from api_database.models.database_create_dto import DatabaseCreateDto from api_database.models.database_dto import DatabaseDto from api_database.models.database_modify_dto import DatabaseModifyDto from api_database.models.granted_authority_dto import GrantedAuthorityDto +from api_database.models.image_brief_dto import ImageBriefDto from api_database.models.image_date_dto import ImageDateDto from api_database.models.image_dto import ImageDto from api_database.models.image_env_item_dto import ImageEnvItemDto from api_database.models.license_dto import LicenseDto from api_database.models.table_dto import TableDto +from api_database.models.user_brief_dto import UserBriefDto from api_database.models.user_dto import UserDto diff --git a/.jupyter/api_database/models/__init__.py b/.jupyter/api_database/models/__init__.py index fc310f7815aff78ac385d120b2af7bbe7fa006ca..918bf85903ca098384536a216e26c12ccfd983ac 100644 --- a/.jupyter/api_database/models/__init__.py +++ b/.jupyter/api_database/models/__init__.py @@ -24,9 +24,11 @@ from api_database.models.database_create_dto import DatabaseCreateDto from api_database.models.database_dto import DatabaseDto from api_database.models.database_modify_dto import DatabaseModifyDto from api_database.models.granted_authority_dto import GrantedAuthorityDto +from api_database.models.image_brief_dto import ImageBriefDto from api_database.models.image_date_dto import ImageDateDto from api_database.models.image_dto import ImageDto from api_database.models.image_env_item_dto import ImageEnvItemDto from api_database.models.license_dto import LicenseDto from api_database.models.table_dto import TableDto +from api_database.models.user_brief_dto import UserBriefDto from api_database.models.user_dto import UserDto diff --git a/.jupyter/api_database/models/column_dto.py b/.jupyter/api_database/models/column_dto.py index 322ef1337f52289620246e04e53e51e13e050367..e4e7f842ad2e7e5730320c4eabed83aa17a09250 100644 --- a/.jupyter/api_database/models/column_dto.py +++ b/.jupyter/api_database/models/column_dto.py @@ -309,7 +309,7 @@ class ColumnDto(object): """ if column_type is None: raise ValueError("Invalid value for `column_type`, must not be `None`") # noqa: E501 - allowed_values = ["ColumnTypeDto.ENUM", "ColumnTypeDto.NUMBER", "ColumnTypeDto.DECIMAL", "ColumnTypeDto.STRING", "ColumnTypeDto.TEXT", "ColumnTypeDto.BOOLEAN", "ColumnTypeDto.DATE", "ColumnTypeDto.TIMESTAMP", "ColumnTypeDto.BLOB"] # noqa: E501 + allowed_values = ["enum", "number", "decimal", "string", "text", "boolean", "date", "timestamp", "blob"] # noqa: E501 if column_type not in allowed_values: raise ValueError( "Invalid value for `column_type` ({0}), must be one of {1}" # noqa: E501 diff --git a/.jupyter/api_database/models/container_dto.py b/.jupyter/api_database/models/container_dto.py index 706454ad0f6198037144ae366647a86b57b0a790..e001f99473d271d4a4be3019a4579931610bbc82 100644 --- a/.jupyter/api_database/models/container_dto.py +++ b/.jupyter/api_database/models/container_dto.py @@ -33,7 +33,7 @@ class ContainerDto(object): 'name': 'str', 'state': 'str', 'databases': 'list[DatabaseDto]', - 'image': 'ImageDto', + 'image': 'ImageBriefDto', 'port': 'int', 'created': 'datetime', 'internal_name': 'str', @@ -210,7 +210,7 @@ class ContainerDto(object): :return: The image of this ContainerDto. # noqa: E501 - :rtype: ImageDto + :rtype: ImageBriefDto """ return self._image @@ -220,7 +220,7 @@ class ContainerDto(object): :param image: The image of this ContainerDto. # noqa: E501 - :type: ImageDto + :type: ImageBriefDto """ self._image = image diff --git a/.jupyter/api_database/models/database_brief_dto.py b/.jupyter/api_database/models/database_brief_dto.py index db5ac743e4693b8bdd683d7db4ad21905d475be1..bf325afda796e5f97d25a0459deba58fefdbad9b 100644 --- a/.jupyter/api_database/models/database_brief_dto.py +++ b/.jupyter/api_database/models/database_brief_dto.py @@ -33,7 +33,7 @@ class DatabaseBriefDto(object): 'description': 'str', 'engine': 'str', 'container': 'ContainerBriefDto', - 'creator': 'UserDto', + 'creator': 'UserBriefDto', 'created': 'datetime', 'is_public': 'bool' } @@ -190,7 +190,7 @@ class DatabaseBriefDto(object): :return: The creator of this DatabaseBriefDto. # noqa: E501 - :rtype: UserDto + :rtype: UserBriefDto """ return self._creator @@ -200,7 +200,7 @@ class DatabaseBriefDto(object): :param creator: The creator of this DatabaseBriefDto. # noqa: E501 - :type: UserDto + :type: UserBriefDto """ self._creator = creator diff --git a/.jupyter/api_identifier/__init__.py b/.jupyter/api_identifier/__init__.py index 78ac494273bbb2a8f5f5beaf0c412d4eccac29ff..588a1b3d2e5d606eceab7ad2a056157c2d000d5d 100644 --- a/.jupyter/api_identifier/__init__.py +++ b/.jupyter/api_identifier/__init__.py @@ -31,6 +31,7 @@ from api_identifier.models.database_dto import DatabaseDto from api_identifier.models.granted_authority_dto import GrantedAuthorityDto from api_identifier.models.identifier_create_dto import IdentifierCreateDto from api_identifier.models.identifier_dto import IdentifierDto +from api_identifier.models.image_brief_dto import ImageBriefDto from api_identifier.models.image_date_dto import ImageDateDto from api_identifier.models.image_dto import ImageDto from api_identifier.models.image_env_item_dto import ImageEnvItemDto diff --git a/.jupyter/api_identifier/models/__init__.py b/.jupyter/api_identifier/models/__init__.py index 8a27b0c6626f164fc0a0a95e711df6389a01bba4..e39aeeecc9d92c71faa0920063ab564ed64ad624 100644 --- a/.jupyter/api_identifier/models/__init__.py +++ b/.jupyter/api_identifier/models/__init__.py @@ -24,6 +24,7 @@ from api_identifier.models.database_dto import DatabaseDto from api_identifier.models.granted_authority_dto import GrantedAuthorityDto from api_identifier.models.identifier_create_dto import IdentifierCreateDto from api_identifier.models.identifier_dto import IdentifierDto +from api_identifier.models.image_brief_dto import ImageBriefDto from api_identifier.models.image_date_dto import ImageDateDto from api_identifier.models.image_dto import ImageDto from api_identifier.models.image_env_item_dto import ImageEnvItemDto diff --git a/.jupyter/api_query/__init__.py b/.jupyter/api_query/__init__.py index 9a4636641eb566a3df6e570d90d93092ad56386c..6856a5d62ced5a39416767fdee3f6b28e00ad8c5 100644 --- a/.jupyter/api_query/__init__.py +++ b/.jupyter/api_query/__init__.py @@ -31,6 +31,7 @@ from api_query.models.container_dto import ContainerDto from api_query.models.database_dto import DatabaseDto from api_query.models.execute_statement_dto import ExecuteStatementDto from api_query.models.granted_authority_dto import GrantedAuthorityDto +from api_query.models.image_brief_dto import ImageBriefDto from api_query.models.image_date_dto import ImageDateDto from api_query.models.image_dto import ImageDto from api_query.models.image_env_item_dto import ImageEnvItemDto diff --git a/.jupyter/api_query/models/__init__.py b/.jupyter/api_query/models/__init__.py index b0fb2f215ecc74d5a89f888c8b55732a85624eee..7aa0df469900aaa50e5a3c84087aed997ebf9324 100644 --- a/.jupyter/api_query/models/__init__.py +++ b/.jupyter/api_query/models/__init__.py @@ -21,6 +21,7 @@ from api_query.models.container_dto import ContainerDto from api_query.models.database_dto import DatabaseDto from api_query.models.execute_statement_dto import ExecuteStatementDto from api_query.models.granted_authority_dto import GrantedAuthorityDto +from api_query.models.image_brief_dto import ImageBriefDto from api_query.models.image_date_dto import ImageDateDto from api_query.models.image_dto import ImageDto from api_query.models.image_env_item_dto import ImageEnvItemDto diff --git a/.jupyter/api_query/models/column_dto.py b/.jupyter/api_query/models/column_dto.py index 4873a95e8de00c9fd8622dd19aeec1010eb97d3a..f6997fd2c20d93994ec83b4d03ec0f925ea30d8d 100644 --- a/.jupyter/api_query/models/column_dto.py +++ b/.jupyter/api_query/models/column_dto.py @@ -309,7 +309,7 @@ class ColumnDto(object): """ if column_type is None: raise ValueError("Invalid value for `column_type`, must not be `None`") # noqa: E501 - allowed_values = ["ColumnTypeDto.ENUM", "ColumnTypeDto.NUMBER", "ColumnTypeDto.DECIMAL", "ColumnTypeDto.STRING", "ColumnTypeDto.TEXT", "ColumnTypeDto.BOOLEAN", "ColumnTypeDto.DATE", "ColumnTypeDto.TIMESTAMP", "ColumnTypeDto.BLOB"] # noqa: E501 + allowed_values = ["enum", "number", "decimal", "string", "text", "boolean", "date", "timestamp", "blob"] # noqa: E501 if column_type not in allowed_values: raise ValueError( "Invalid value for `column_type` ({0}), must be one of {1}" # noqa: E501 diff --git a/.jupyter/api_query/models/container_dto.py b/.jupyter/api_query/models/container_dto.py index 8fa4c77d8b16a48b347becf13b2e1cd97183243f..6776f130533acac43b95188fa3a834a96c3e4c97 100644 --- a/.jupyter/api_query/models/container_dto.py +++ b/.jupyter/api_query/models/container_dto.py @@ -33,7 +33,7 @@ class ContainerDto(object): 'name': 'str', 'state': 'str', 'databases': 'list[DatabaseDto]', - 'image': 'ImageDto', + 'image': 'ImageBriefDto', 'port': 'int', 'created': 'datetime', 'internal_name': 'str', @@ -210,7 +210,7 @@ class ContainerDto(object): :return: The image of this ContainerDto. # noqa: E501 - :rtype: ImageDto + :rtype: ImageBriefDto """ return self._image @@ -220,7 +220,7 @@ class ContainerDto(object): :param image: The image of this ContainerDto. # noqa: E501 - :type: ImageDto + :type: ImageBriefDto """ self._image = image diff --git a/.jupyter/api_table/__init__.py b/.jupyter/api_table/__init__.py index 0dbf949a9587e9106bb0233da33185a01f1d5ffb..beaceff5a74771307782fed7d105c64f63e06540 100644 --- a/.jupyter/api_table/__init__.py +++ b/.jupyter/api_table/__init__.py @@ -24,7 +24,9 @@ from api_table.models.api_error_dto import ApiErrorDto from api_table.models.column_create_dto import ColumnCreateDto from api_table.models.column_dto import ColumnDto from api_table.models.concept_dto import ConceptDto +from api_table.models.granted_authority_dto import GrantedAuthorityDto from api_table.models.image_date_dto import ImageDateDto from api_table.models.table_brief_dto import TableBriefDto from api_table.models.table_create_dto import TableCreateDto from api_table.models.table_dto import TableDto +from api_table.models.user_brief_dto import UserBriefDto diff --git a/.jupyter/api_table/models/__init__.py b/.jupyter/api_table/models/__init__.py index e5aef82457dd5905b4b4c5d358fa52222b64dcdc..c7a86dfadf16101370f746da857eca3f44cac6a1 100644 --- a/.jupyter/api_table/models/__init__.py +++ b/.jupyter/api_table/models/__init__.py @@ -18,7 +18,9 @@ from api_table.models.api_error_dto import ApiErrorDto from api_table.models.column_create_dto import ColumnCreateDto from api_table.models.column_dto import ColumnDto from api_table.models.concept_dto import ConceptDto +from api_table.models.granted_authority_dto import GrantedAuthorityDto from api_table.models.image_date_dto import ImageDateDto from api_table.models.table_brief_dto import TableBriefDto from api_table.models.table_create_dto import TableCreateDto from api_table.models.table_dto import TableDto +from api_table.models.user_brief_dto import UserBriefDto diff --git a/.jupyter/api_table/models/table_brief_dto.py b/.jupyter/api_table/models/table_brief_dto.py index cb8776e8d48884bc8d9e51db952c7fc7bae8aaf0..a811499bd935581d4021a5783ffd3c5490a9907c 100644 --- a/.jupyter/api_table/models/table_brief_dto.py +++ b/.jupyter/api_table/models/table_brief_dto.py @@ -30,23 +30,27 @@ class TableBriefDto(object): swagger_types = { 'id': 'int', 'name': 'str', + 'creator': 'UserBriefDto', 'internal_name': 'str' } attribute_map = { 'id': 'id', 'name': 'name', + 'creator': 'creator', 'internal_name': 'internal_name' } - def __init__(self, id=None, name=None, internal_name=None): # noqa: E501 + def __init__(self, id=None, name=None, creator=None, internal_name=None): # noqa: E501 """TableBriefDto - a model defined in Swagger""" # noqa: E501 self._id = None self._name = None + self._creator = None self._internal_name = None self.discriminator = None self.id = id self.name = name + self.creator = creator self.internal_name = internal_name @property @@ -95,6 +99,29 @@ class TableBriefDto(object): self._name = name + @property + def creator(self): + """Gets the creator of this TableBriefDto. # noqa: E501 + + + :return: The creator of this TableBriefDto. # noqa: E501 + :rtype: UserBriefDto + """ + return self._creator + + @creator.setter + def creator(self, creator): + """Sets the creator of this TableBriefDto. + + + :param creator: The creator of this TableBriefDto. # noqa: E501 + :type: UserBriefDto + """ + if creator is None: + raise ValueError("Invalid value for `creator`, must not be `None`") # noqa: E501 + + self._creator = creator + @property def internal_name(self): """Gets the internal_name of this TableBriefDto. # noqa: E501 diff --git a/Makefile b/Makefile index 7d57c171c4ac14797f23f00b754b835065968d21..b14b74581ea14ee0605d822559a656c935f3f428 100644 --- a/Makefile +++ b/Makefile @@ -44,7 +44,7 @@ build-backend-query: build-backend-metadata build-backend-table: build-backend-metadata mvn -f ./fda-table-service/pom.xml clean package -DskipTests -build-backend: build-backend-metadata build-backend-authentication build-backend-container build-backend-database build-backend-discovery build-backend-gateway build-backend-query build-backend-table build-backend-identifier +build-backend: build-backend-metadata build-backend-database build-backend-query build-backend-table build-backend-identifier build-backend-authentication build-backend-container build-backend-discovery build-backend-gateway build-docker: docker-compose build fda-metadata-db diff --git a/docker-compose.prod.yml b/docker-compose.prod.yml index 92b8dc495767193d91bcc405d39ed55785803959..be516fe2243249fb671a686bd997e4ffb2fd0ddd 100644 --- a/docker-compose.prod.yml +++ b/docker-compose.prod.yml @@ -142,8 +142,10 @@ services: JWT_SECRET: fda-secret JWT_EXPIRATION: 86400000 SPRING_PROFILES_ACTIVE: docker - SMTP_USERNAME: ${MAIL_USERNAME:-local} - SMTP_PASSWORD: ${MAIL_PASSWORD:-local} + SMTP_HOST: ${MAIL_HOST} + SMTP_PORT: ${MAIL_PORT} + SMTP_USERNAME: ${MAIL_USERNAME} + SMTP_PASSWORD: ${MAIL_PASSWORD} TZ: Europe/Vienna ports: - "9097:9097" diff --git a/docker-compose.yml b/docker-compose.yml index 12f024d9a771679ea9a8095ed32949b067c56359..edaeca5bcb33e2c57367bec30a84b7f24d52e365 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -142,10 +142,10 @@ services: JWT_SECRET: fda-secret JWT_EXPIRATION: 86400000 SPRING_PROFILES_ACTIVE: docker - SMTP_HOST: ${MAIL_HOST} - SMTP_PORT: ${MAIL_PORT} - SMTP_USERNAME: ${MAIL_USERNAME:-local} - SMTP_PASSWORD: ${MAIL_PASSWORD:-local} + SMTP_HOST: "" + SMTP_PORT: "" + SMTP_USERNAME: "" + SMTP_PASSWORD: "" TZ: Europe/Vienna ports: - "9097:9097" diff --git a/fda-container-service/services/src/main/java/at/tuwien/service/impl/ContainerServiceImpl.java b/fda-container-service/services/src/main/java/at/tuwien/service/impl/ContainerServiceImpl.java index d4ab2f1e5908e79c774b1835dc3a0473e4f77304..20e7a4df8d9ee0b3c914adb96d33091c525065ec 100644 --- a/fda-container-service/services/src/main/java/at/tuwien/service/impl/ContainerServiceImpl.java +++ b/fda-container-service/services/src/main/java/at/tuwien/service/impl/ContainerServiceImpl.java @@ -86,7 +86,7 @@ public class ContainerServiceImpl implements ContainerService { .exec(); log.info("Created volume {}", response.getName()); log.debug("created volume {} with mapping /var/lib/mysql", response.getName()); - final Volume volume = new Volume("/var/lib/mysql"); +// final Volume volume = new Volume("/var/lib/mysql"); /* create the container */ final CreateContainerResponse response1; try { @@ -96,7 +96,7 @@ public class ContainerServiceImpl implements ContainerService { .withHostName(container.getInternalName()) .withEnv(imageMapper.environmentItemsToStringList(image.get().getEnvironment())) .withHostConfig(hostConfig) - .withVolumes(volume) +// .withVolumes(volume) .exec(); } catch (ConflictException e) { log.error("Conflicting names {}", createDto.getName()); diff --git a/fda-database-service/pom.xml b/fda-database-service/pom.xml index 56681cae00cb2bb4b63fff62a567366079f0cd40..7ed4cc3da853ad276e63decef649b7a22159ee48 100644 --- a/fda-database-service/pom.xml +++ b/fda-database-service/pom.xml @@ -89,7 +89,7 @@ <artifactId>mysql-connector-java</artifactId> <version>${mysql.version}</version> </dependency> - <!-- Entity and API --> + <!-- Entity, API, QueryStore --> <dependency> <groupId>at.tuwien</groupId> <artifactId>fda-metadata-db-api</artifactId> @@ -102,6 +102,12 @@ <version>${project.version}</version> <scope>compile</scope> </dependency> + <dependency> + <groupId>at.tuwien</groupId> + <artifactId>fda-metadata-db-querystore</artifactId> + <version>${project.version}</version> + <scope>compile</scope> + </dependency> <dependency> <groupId>org.hibernate</groupId> <artifactId>hibernate-c3p0</artifactId> diff --git a/fda-database-service/rest-service/src/main/java/at/tuwien/endpoints/ContainerDatabaseEndpoint.java b/fda-database-service/rest-service/src/main/java/at/tuwien/endpoints/ContainerDatabaseEndpoint.java index fea63c6d1f19540efee2f2deac7ec809a67db888..66785334812ba2c7439d01336dbe31bd6c22e98c 100644 --- a/fda-database-service/rest-service/src/main/java/at/tuwien/endpoints/ContainerDatabaseEndpoint.java +++ b/fda-database-service/rest-service/src/main/java/at/tuwien/endpoints/ContainerDatabaseEndpoint.java @@ -8,6 +8,7 @@ import at.tuwien.entities.database.Database; import at.tuwien.exception.*; import at.tuwien.mapper.DatabaseMapper; import at.tuwien.service.MessageQueueService; +import at.tuwien.service.QueryStoreService; import at.tuwien.service.impl.MariaDbServiceImpl; import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.security.SecurityRequirement; @@ -33,31 +34,33 @@ public class ContainerDatabaseEndpoint { private final DatabaseMapper databaseMapper; private final MariaDbServiceImpl databaseService; + private final QueryStoreService queryStoreService; private final MessageQueueService messageQueueService; @Autowired public ContainerDatabaseEndpoint(DatabaseMapper databaseMapper, MariaDbServiceImpl databaseService, - MessageQueueService messageQueueService) { + QueryStoreService queryStoreService, MessageQueueService messageQueueService) { this.databaseMapper = databaseMapper; this.databaseService = databaseService; + this.queryStoreService = queryStoreService; this.messageQueueService = messageQueueService; } @GetMapping @Transactional(readOnly = true) @Operation(summary = "List databases") - public ResponseEntity<List<DatabaseBriefDto>> findAll(@NotBlank @PathVariable("id") Long id, + public ResponseEntity<List<DatabaseBriefDto>> findAll(@NotBlank @PathVariable("id") Long containerId, Principal principal) { final List<DatabaseBriefDto> databases; if (principal == null) { log.trace("principal missing, listing all public databases only"); - databases = databaseService.findAllPublic(id) + databases = databaseService.findAllPublic(containerId) .stream() .map(databaseMapper::databaseToDatabaseBriefDto) .collect(Collectors.toList()); } else { log.trace("principal present, listing all public databases and my private databases"); - databases = databaseService.findAllPublicOrMine(id, principal) + databases = databaseService.findAllPublicOrMine(containerId, principal) .stream() .map(databaseMapper::databaseToDatabaseBriefDto) .collect(Collectors.toList()); @@ -71,13 +74,15 @@ public class ContainerDatabaseEndpoint { @Transactional @PreAuthorize("hasRole('ROLE_RESEARCHER')") @Operation(summary = "Create database", security = @SecurityRequirement(name = "bearerAuth")) - public ResponseEntity<DatabaseBriefDto> create(@NotBlank @PathVariable("id") Long id, + public ResponseEntity<DatabaseBriefDto> create(@NotBlank @PathVariable("id") Long containerId, @Valid @RequestBody DatabaseCreateDto createDto, Principal principal) throws ImageNotSupportedException, ContainerNotFoundException, DatabaseMalformedException, - AmqpException, ContainerConnectionException, UserNotFoundException { - final Database database = databaseService.create(id, createDto, principal); + AmqpException, ContainerConnectionException, UserNotFoundException, QueryStoreException, + DatabaseNotFoundException, DatabaseNameExistsException { + final Database database = databaseService.create(containerId, createDto, principal); messageQueueService.createExchange(database, principal); + queryStoreService.create(containerId, database.getId()); return ResponseEntity.status(HttpStatus.CREATED) .body(databaseMapper.databaseToDatabaseBriefDto(database)); } @@ -86,11 +91,11 @@ public class ContainerDatabaseEndpoint { @Transactional @PreAuthorize("hasRole('ROLE_RESEARCHER')") @Operation(summary = "Update database", security = @SecurityRequirement(name = "bearerAuth")) - public ResponseEntity<DatabaseBriefDto> update(@NotBlank @PathVariable("id") Long id, + public ResponseEntity<DatabaseBriefDto> update(@NotBlank @PathVariable("id") Long containerId, @NotBlank @PathVariable Long databaseId, @Valid @RequestBody DatabaseModifyDto modifyDto) throws UserNotFoundException, DatabaseNotFoundException, LicenseNotFoundException { - final Database database = databaseService.modify(id, databaseId, modifyDto); + final Database database = databaseService.modify(containerId, databaseId, modifyDto); return ResponseEntity.status(HttpStatus.ACCEPTED) .body(databaseMapper.databaseToDatabaseBriefDto(database)); } diff --git a/fda-database-service/rest-service/src/main/java/at/tuwien/handlers/ApiExceptionHandler.java b/fda-database-service/rest-service/src/main/java/at/tuwien/handlers/ApiExceptionHandler.java index 24e99d7dc2fea3ceafe30ff90e663e98bdb81a66..7ae0ee9ec6d38e22a27bb051cfc91ad4d5c9abce 100644 --- a/fda-database-service/rest-service/src/main/java/at/tuwien/handlers/ApiExceptionHandler.java +++ b/fda-database-service/rest-service/src/main/java/at/tuwien/handlers/ApiExceptionHandler.java @@ -80,6 +80,17 @@ public class ApiExceptionHandler extends ResponseEntityExceptionHandler { return new ResponseEntity<>(response, new HttpHeaders(), response.getStatus()); } + @ResponseStatus(HttpStatus.CONFLICT) + @ExceptionHandler(DatabaseNameExistsException.class) + public ResponseEntity<ApiErrorDto> handle(DatabaseNameExistsException e, WebRequest request) { + final ApiErrorDto response = ApiErrorDto.builder() + .status(HttpStatus.CONFLICT) + .message(e.getLocalizedMessage()) + .code("error.database.name_exists") + .build(); + return new ResponseEntity<>(response, new HttpHeaders(), response.getStatus()); + } + @ResponseStatus(HttpStatus.NOT_FOUND) @ExceptionHandler(DatabaseNotFoundException.class) public ResponseEntity<ApiErrorDto> handle(DatabaseNotFoundException e, WebRequest request) { @@ -102,4 +113,48 @@ public class ApiExceptionHandler extends ResponseEntityExceptionHandler { return new ResponseEntity<>(response, new HttpHeaders(), response.getStatus()); } + @ResponseStatus(HttpStatus.NOT_FOUND) + @ExceptionHandler(LicenseNotFoundException.class) + public ResponseEntity<ApiErrorDto> handle(LicenseNotFoundException e, WebRequest request) { + final ApiErrorDto response = ApiErrorDto.builder() + .status(HttpStatus.NOT_FOUND) + .message(e.getLocalizedMessage()) + .code("error.license.notfound") + .build(); + return new ResponseEntity<>(response, new HttpHeaders(), response.getStatus()); + } + + @ResponseStatus(HttpStatus.CONFLICT) + @ExceptionHandler(QueryStoreException.class) + public ResponseEntity<ApiErrorDto> handle(QueryStoreException e, WebRequest request) { + final ApiErrorDto response = ApiErrorDto.builder() + .status(HttpStatus.CONFLICT) + .message(e.getLocalizedMessage()) + .code("error.querystore.remote") + .build(); + return new ResponseEntity<>(response, new HttpHeaders(), response.getStatus()); + } + + @ResponseStatus(HttpStatus.NOT_FOUND) + @ExceptionHandler(SubjectNotFoundException.class) + public ResponseEntity<ApiErrorDto> handle(SubjectNotFoundException e, WebRequest request) { + final ApiErrorDto response = ApiErrorDto.builder() + .status(HttpStatus.NOT_FOUND) + .message(e.getLocalizedMessage()) + .code("error.subject.notfound") + .build(); + return new ResponseEntity<>(response, new HttpHeaders(), response.getStatus()); + } + + @ResponseStatus(HttpStatus.NOT_FOUND) + @ExceptionHandler(UserNotFoundException.class) + public ResponseEntity<ApiErrorDto> handle(UserNotFoundException e, WebRequest request) { + final ApiErrorDto response = ApiErrorDto.builder() + .status(HttpStatus.NOT_FOUND) + .message(e.getLocalizedMessage()) + .code("error.user.notfound") + .build(); + return new ResponseEntity<>(response, new HttpHeaders(), response.getStatus()); + } + } \ No newline at end of file diff --git a/fda-database-service/rest-service/src/main/resources/mariadb_hibernate.cfg.xml b/fda-database-service/rest-service/src/main/resources/mariadb_hibernate.cfg.xml index 9193ed80caa051c2f993db9124c2cbad1f69a01a..5d1f8bd44e7e2f044e1effb24191f4262d83dd52 100644 --- a/fda-database-service/rest-service/src/main/resources/mariadb_hibernate.cfg.xml +++ b/fda-database-service/rest-service/src/main/resources/mariadb_hibernate.cfg.xml @@ -13,5 +13,8 @@ <property name="show_sql">true</property> <property name="format_sql">true</property> <property name="hbm2ddl.auto">update</property> + <mapping class="at.tuwien.querystore.Query" /> + <mapping class="at.tuwien.querystore.Table" /> + <mapping class="at.tuwien.querystore.Column" /> </session-factory> </hibernate-configuration> diff --git a/fda-database-service/services/src/main/java/at/tuwien/exception/DatabaseNameExistsException.java b/fda-database-service/services/src/main/java/at/tuwien/exception/DatabaseNameExistsException.java new file mode 100644 index 0000000000000000000000000000000000000000..68e194813806a1a5f3ecd8c36983e6e6a798acfc --- /dev/null +++ b/fda-database-service/services/src/main/java/at/tuwien/exception/DatabaseNameExistsException.java @@ -0,0 +1,23 @@ +package at.tuwien.exception; + +import org.springframework.http.HttpStatus; +import org.springframework.web.bind.annotation.ResponseStatus; + +import java.io.IOException; + +@ResponseStatus(code = HttpStatus.CONFLICT) +public class DatabaseNameExistsException extends IOException { + + public DatabaseNameExistsException(String msg) { + super(msg); + } + + public DatabaseNameExistsException(String msg, Throwable thr) { + super(msg, thr); + } + + public DatabaseNameExistsException(Throwable thr) { + super(thr); + } + +} diff --git a/fda-database-service/services/src/main/java/at/tuwien/exception/QueryStoreException.java b/fda-database-service/services/src/main/java/at/tuwien/exception/QueryStoreException.java new file mode 100644 index 0000000000000000000000000000000000000000..d07cd0df4aa239005ecfdefec438d9305dcfa818 --- /dev/null +++ b/fda-database-service/services/src/main/java/at/tuwien/exception/QueryStoreException.java @@ -0,0 +1,19 @@ +package at.tuwien.exception; + +import org.springframework.http.HttpStatus; +import org.springframework.web.bind.annotation.ResponseStatus; + +@ResponseStatus(code = HttpStatus.CONFLICT) +public class QueryStoreException extends Exception { + + public QueryStoreException(String msg) { + super(msg); + } + + public QueryStoreException(String msg, Throwable thr) { + super(msg, thr); + } + + public QueryStoreException(Throwable thr) { super(thr); + } +} diff --git a/fda-database-service/services/src/main/java/at/tuwien/service/DatabaseService.java b/fda-database-service/services/src/main/java/at/tuwien/service/DatabaseService.java index 471b2c1f73efc931fdd0b4dd47292a4fa5720f18..31d638e39ccef219914e91f63cb53d411ba30bc8 100644 --- a/fda-database-service/services/src/main/java/at/tuwien/service/DatabaseService.java +++ b/fda-database-service/services/src/main/java/at/tuwien/service/DatabaseService.java @@ -80,7 +80,7 @@ public interface DatabaseService { */ Database create(Long id, DatabaseCreateDto createDto, Principal principal) throws ImageNotSupportedException, ContainerNotFoundException, - DatabaseMalformedException, AmqpException, ContainerConnectionException, UserNotFoundException; + DatabaseMalformedException, AmqpException, ContainerConnectionException, UserNotFoundException, DatabaseNameExistsException; /** * Updates the database metadata. diff --git a/fda-database-service/services/src/main/java/at/tuwien/service/QueryStoreService.java b/fda-database-service/services/src/main/java/at/tuwien/service/QueryStoreService.java new file mode 100644 index 0000000000000000000000000000000000000000..2cd4393139da368641f141775f34c0bc32f44436 --- /dev/null +++ b/fda-database-service/services/src/main/java/at/tuwien/service/QueryStoreService.java @@ -0,0 +1,20 @@ +package at.tuwien.service; + +import at.tuwien.exception.DatabaseNotFoundException; +import at.tuwien.exception.ImageNotSupportedException; +import at.tuwien.exception.QueryStoreException; + +public interface QueryStoreService { + + /** + * Creates the query store by executing a query, the Hibernate session is configured to automatically create the necessary table. + * + * @param containerId The container id. + * @param databaseId The database id. + * @throws DatabaseNotFoundException The database was not found in the metadata database. + * @throws ImageNotSupportedException The image is not supported, currently we only support MariaDB. + * @throws QueryStoreException The query store failed to create. + */ + void create(Long containerId, Long databaseId) throws DatabaseNotFoundException, + ImageNotSupportedException, QueryStoreException; +} diff --git a/fda-database-service/services/src/main/java/at/tuwien/service/impl/HibernateConnector.java b/fda-database-service/services/src/main/java/at/tuwien/service/impl/HibernateConnector.java index d958b7381a1c12c14967466323dbcbc06327f158..1226866d4aadd050153c3ab544470af7cd93f512 100644 --- a/fda-database-service/services/src/main/java/at/tuwien/service/impl/HibernateConnector.java +++ b/fda-database-service/services/src/main/java/at/tuwien/service/impl/HibernateConnector.java @@ -5,21 +5,31 @@ import at.tuwien.entities.container.image.ContainerImage; import at.tuwien.entities.container.image.ContainerImageEnvironmentItem; import at.tuwien.entities.container.image.ContainerImageEnvironmentItemType; import at.tuwien.entities.database.Database; +import at.tuwien.querystore.Column; +import at.tuwien.querystore.Query; +import at.tuwien.querystore.Table; import lombok.extern.log4j.Log4j2; import org.hibernate.Session; import org.hibernate.SessionFactory; import org.hibernate.cfg.Configuration; +import org.hibernate.query.NativeQuery; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; +import javax.persistence.PersistenceException; +import java.util.List; import java.util.stream.Collectors; @Log4j2 @Service public abstract class HibernateConnector { + protected static Session getCurrentSession(ContainerImage image, Container container) { + return getCurrentSession(image, container, null); + } + protected static Session getCurrentSession(ContainerImage image, Container container, Database database) { - final String url = "jdbc:" + image.getJdbcMethod() + "://" + container.getInternalName() + "/" + database.getInternalName(); + final String url = "jdbc:" + image.getJdbcMethod() + "://" + container.getInternalName() + "/" + (database != null ? database.getInternalName() : ""); final String username = image.getEnvironment() .stream() .filter(e -> e.getType().equals(ContainerImageEnvironmentItemType.PRIVILEGED_USERNAME)) @@ -32,7 +42,6 @@ public abstract class HibernateConnector { .map(ContainerImageEnvironmentItem::getValue) .collect(Collectors.toList()) .get(0); - final Configuration config = new Configuration(); config.configure("mariadb_hibernate.cfg.xml"); config.setProperty("hibernate.connection.url", url); @@ -49,5 +58,20 @@ public abstract class HibernateConnector { return session; } + protected static Long activeConnection(Session session) { + final NativeQuery<?> nativeQuery = session.createSQLQuery("SHOW STATUS LIKE 'threads_connected'"); + final List<?> result; + try { + result = nativeQuery.getResultList(); + } catch (PersistenceException e) { + log.error("Failed to collect number of used connections"); + /* ignore */ + return null; + } + final Object[] row = (Object[]) result.get(0); + log.debug("current number of connections: {}", Long.parseLong(String.valueOf(row[1]))); + return Long.parseLong(String.valueOf(row[1])); + } + } diff --git a/fda-database-service/services/src/main/java/at/tuwien/service/impl/MariaDbServiceImpl.java b/fda-database-service/services/src/main/java/at/tuwien/service/impl/MariaDbServiceImpl.java index 7d21a2d923b4748acd04242f1d18001e5bdb6833..b89eeddbc634d22809d58d9ebdd9b4bfed11cf64 100644 --- a/fda-database-service/services/src/main/java/at/tuwien/service/impl/MariaDbServiceImpl.java +++ b/fda-database-service/services/src/main/java/at/tuwien/service/impl/MariaDbServiceImpl.java @@ -9,7 +9,6 @@ import at.tuwien.entities.user.User; import at.tuwien.exception.*; import at.tuwien.mapper.AmqpMapper; import at.tuwien.mapper.DatabaseMapper; -import at.tuwien.repository.jpa.ContainerRepository; import at.tuwien.repository.jpa.DatabaseRepository; import at.tuwien.repository.elastic.DatabaseidxRepository; import at.tuwien.service.ContainerService; @@ -40,25 +39,20 @@ public class MariaDbServiceImpl extends HibernateConnector implements DatabaseSe private final UserService userService; private final LicenseService licenseService; private final DatabaseMapper databaseMapper; - private final RabbitMqServiceImpl amqpService; private final ContainerService containerService; private final DatabaseRepository databaseRepository; - private final ContainerRepository containerRepository; private final DatabaseidxRepository databaseidxRepository; @Autowired public MariaDbServiceImpl(AmqpMapper amqpMapper, UserService userService, LicenseService licenseService, - DatabaseMapper databaseMapper, RabbitMqServiceImpl amqpService, - ContainerService containerService, DatabaseRepository databaseRepository, - ContainerRepository containerRepository, DatabaseidxRepository databaseidxRepository) { + DatabaseMapper databaseMapper, ContainerService containerService, + DatabaseRepository databaseRepository, DatabaseidxRepository databaseidxRepository) { this.amqpMapper = amqpMapper; this.userService = userService; this.licenseService = licenseService; this.databaseMapper = databaseMapper; - this.amqpService = amqpService; this.containerService = containerService; this.databaseRepository = databaseRepository; - this.containerRepository = containerRepository; this.databaseidxRepository = databaseidxRepository; } @@ -117,51 +111,65 @@ public class MariaDbServiceImpl extends HibernateConnector implements DatabaseSe final Session session = getCurrentSession(container.getImage(), container, database); final Transaction transaction = session.beginTransaction(); final NativeQuery<?> query = session.createSQLQuery(databaseMapper.databaseToRawDeleteDatabaseQuery(database)); - try (session) { + try { log.debug("query affected {} rows", query.executeUpdate()); + activeConnection(session); transaction.commit(); } catch (ServiceException e) { log.error("Failed to delete database."); + session.close(); throw new DatabaseMalformedException("Failed to delete database", e); + } finally { + if (session.isOpen()) { + session.close(); + } } database.setDeleted(Instant.now()) /* method has void, only for debug logs */; /* save in metadata database */ databaseRepository.deleteById(databaseId); log.info("Deleted database with id {}", databaseId); log.debug("deleted database {}", database); - amqpService.deleteExchange(database); - log.debug("deleted exchange {}", database.getExchange()); } @Override @Transactional public Database create(Long containerId, DatabaseCreateDto createDto, Principal principal) throws ImageNotSupportedException, ContainerNotFoundException, - DatabaseMalformedException, AmqpException, ContainerConnectionException, UserNotFoundException { + DatabaseMalformedException, AmqpException, ContainerConnectionException, UserNotFoundException, DatabaseNameExistsException { final Container container = containerService.find(containerId); + if (container.getDatabases().size() != 0) { + log.error("Currently we only support one database per container."); + throw new DatabaseMalformedException("Currently only one database per container is supported"); + } /* start the object */ final Database database = new Database(); database.setName(createDto.getName()); database.setInternalName(databaseMapper.nameToInternalName(database.getName())); database.setContainer(container); /* run query */ - final Session session = getCurrentSession(container.getImage(), container, database); + final Session session = getCurrentSession(container.getImage(), container); final Transaction transaction = session.beginTransaction(); final NativeQuery<?> query = session.createSQLQuery(databaseMapper.databaseToRawCreateDatabaseQuery(database)); try { log.debug("query affected {} rows", query.executeUpdate()); } catch (PersistenceException e) { - log.error("Failed to delete database."); + log.error("Failed to create database"); + log.debug("failed to create database: {}", e.getMessage()); session.close(); - throw new DatabaseMalformedException("Failed to delete database", e); + throw new DatabaseNameExistsException("Failed to create database", e); } final NativeQuery<?> grant = session.createSQLQuery(databaseMapper.imageToRawGrantReadonlyAccessQuery()); - try (session) { + try { log.debug("grant affected {} rows", grant.executeUpdate()); + activeConnection(session); transaction.commit(); } catch (PersistenceException e) { log.error("Failed to grant privileges."); throw new DatabaseMalformedException("Failed to grant privileges", e); + } finally { + if (session.isOpen()) { + session.close(); + } } /* save in metadata database */ database.setExchange(amqpMapper.exchangeName(database)); @@ -173,9 +181,7 @@ public class MariaDbServiceImpl extends HibernateConnector implements DatabaseSe log.info("Created database with id {}", out.getId()); log.debug("created database {}", out); // save in database_index - elastic search - databaseidxRepository.save(database); - amqpService.createExchange(database, principal); - log.debug("created exchange {}", database.getExchange()); +// databaseidxRepository.save(database); return out; } diff --git a/fda-database-service/services/src/main/java/at/tuwien/service/impl/QueryStoreServiceImpl.java b/fda-database-service/services/src/main/java/at/tuwien/service/impl/QueryStoreServiceImpl.java new file mode 100644 index 0000000000000000000000000000000000000000..5258ebbce18f069806a79ad48f5f155d69993c33 --- /dev/null +++ b/fda-database-service/services/src/main/java/at/tuwien/service/impl/QueryStoreServiceImpl.java @@ -0,0 +1,56 @@ +package at.tuwien.service.impl; + +import at.tuwien.entities.database.Database; +import at.tuwien.exception.DatabaseNotFoundException; +import at.tuwien.exception.ImageNotSupportedException; +import at.tuwien.exception.QueryStoreException; +import at.tuwien.service.DatabaseService; +import at.tuwien.service.QueryStoreService; +import lombok.extern.log4j.Log4j2; +import org.hibernate.Session; +import org.hibernate.Transaction; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; + +import javax.persistence.PersistenceException; + +@Log4j2 +@Service +public class QueryStoreServiceImpl extends HibernateConnector implements QueryStoreService { + + private final DatabaseService databaseService; + + @Autowired + public QueryStoreServiceImpl(DatabaseService databaseService) { + this.databaseService = databaseService; + } + + @Override + public void create(Long containerId, Long databaseId) throws DatabaseNotFoundException, + ImageNotSupportedException, QueryStoreException { + /* find */ + final Database database = databaseService.findById(containerId, databaseId); + if (!database.getContainer().getImage().getRepository().equals("mariadb")) { + throw new ImageNotSupportedException("Currently only MariaDB is supported"); + } + /* run query */ + final Session session = getCurrentSession(database.getContainer().getImage(), database.getContainer(), database); + final Transaction transaction = session.beginTransaction(); + /* use jpq to select all */ + final org.hibernate.query.Query<at.tuwien.querystore.Query> queries = session.createQuery("select q from Query q", + at.tuwien.querystore.Query.class); + try { + queries.getResultList(); + activeConnection(session); + transaction.commit(); + } catch (PersistenceException e) { + log.error("Failed to find all queries"); + session.close(); + throw new QueryStoreException("Failed to find all queries"); + } finally { + if (session.isOpen()) { + session.close(); + } + } + } +} diff --git a/fda-metadata-db/Dockerfile b/fda-metadata-db/Dockerfile index c2cb1a1a8fbc5475c88ec7cccb8e83529d8c7ffc..b192803c5aaf9baf2576a1af33fa3da6ce227555 100644 --- a/fda-metadata-db/Dockerfile +++ b/fda-metadata-db/Dockerfile @@ -8,6 +8,7 @@ RUN mvn -fn -B dependency:go-offline > /dev/null COPY ./api ./api COPY ./entities ./entities +COPY ./querystore ./querystore # Make sure it compiles RUN mvn -q clean package -DskipTests > /dev/null diff --git a/fda-metadata-db/api/src/main/java/at/tuwien/api/container/ContainerBriefDto.java b/fda-metadata-db/api/src/main/java/at/tuwien/api/container/ContainerBriefDto.java index 3e194ce2e7557e10bb8bea6f76a567c74a832fb3..76d29bbde92feb6fcebcdaef580ca5b6c5b3af81 100644 --- a/fda-metadata-db/api/src/main/java/at/tuwien/api/container/ContainerBriefDto.java +++ b/fda-metadata-db/api/src/main/java/at/tuwien/api/container/ContainerBriefDto.java @@ -1,5 +1,6 @@ package at.tuwien.api.container; +import at.tuwien.api.user.UserBriefDto; import at.tuwien.api.user.UserDto; import com.fasterxml.jackson.annotation.JsonFormat; import com.fasterxml.jackson.annotation.JsonIgnore; @@ -33,7 +34,7 @@ public class ContainerBriefDto { private String name; @Parameter(name = "container creator") - private UserDto creator; + private UserBriefDto creator; @NotBlank @JsonProperty("internal_name") diff --git a/fda-metadata-db/api/src/main/java/at/tuwien/api/container/ContainerDto.java b/fda-metadata-db/api/src/main/java/at/tuwien/api/container/ContainerDto.java index be5a6dc123b368919792a4f63b6ad82e7dfe2ce9..4cf8f13ec2fcc48353d759ef4ef8378c6e394cdc 100644 --- a/fda-metadata-db/api/src/main/java/at/tuwien/api/container/ContainerDto.java +++ b/fda-metadata-db/api/src/main/java/at/tuwien/api/container/ContainerDto.java @@ -1,5 +1,6 @@ package at.tuwien.api.container; +import at.tuwien.api.container.image.ImageBriefDto; import at.tuwien.api.container.image.ImageDto; import at.tuwien.api.database.DatabaseDto; import at.tuwien.api.user.UserDto; @@ -53,7 +54,7 @@ public class ContainerDto { private Boolean isPublic; @Parameter(name = "container image") - private ImageDto image; + private ImageBriefDto image; @Parameter(name = "container port") private Integer port; diff --git a/fda-metadata-db/api/src/main/java/at/tuwien/api/database/DatabaseBriefDto.java b/fda-metadata-db/api/src/main/java/at/tuwien/api/database/DatabaseBriefDto.java index 08249907c78fe7138ae30a6b48c567713ea5f2fc..813f9af3c0183431f37fdc08220cd57ccc6d24e5 100644 --- a/fda-metadata-db/api/src/main/java/at/tuwien/api/database/DatabaseBriefDto.java +++ b/fda-metadata-db/api/src/main/java/at/tuwien/api/database/DatabaseBriefDto.java @@ -2,6 +2,7 @@ package at.tuwien.api.database; import at.tuwien.api.container.ContainerBriefDto; import at.tuwien.api.container.ContainerDto; +import at.tuwien.api.user.UserBriefDto; import at.tuwien.api.user.UserDto; import com.fasterxml.jackson.annotation.JsonFormat; import com.fasterxml.jackson.annotation.JsonProperty; @@ -42,7 +43,7 @@ public class DatabaseBriefDto { private ContainerBriefDto container; @Parameter(name = "database creator") - private UserDto creator; + private UserBriefDto creator; @Parameter(name = "database creation time", example = "2020-08-04 11:12:00") @JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyy-MM-dd'T'HH:mm:ss.SSSXXX", timezone = "UTC") diff --git a/fda-metadata-db/api/src/main/java/at/tuwien/api/database/table/TableBriefDto.java b/fda-metadata-db/api/src/main/java/at/tuwien/api/database/table/TableBriefDto.java index e0046c0a2ac28151ebfa84252ce323e9624d4808..dcbc043ca21212c4c8d558c698c2769da89db825 100644 --- a/fda-metadata-db/api/src/main/java/at/tuwien/api/database/table/TableBriefDto.java +++ b/fda-metadata-db/api/src/main/java/at/tuwien/api/database/table/TableBriefDto.java @@ -1,5 +1,6 @@ package at.tuwien.api.database.table; +import at.tuwien.api.user.UserBriefDto; import com.fasterxml.jackson.annotation.JsonProperty; import io.swagger.v3.oas.annotations.Parameter; import lombok.*; @@ -28,4 +29,8 @@ public class TableBriefDto { @Parameter(name = "table internal name", example = "weather_australia") private String internalName; + @NotNull(message = "creator is required") + @Parameter(name = "table creator") + private UserBriefDto creator; + } diff --git a/fda-metadata-db/api/src/main/java/at/tuwien/api/database/table/columns/ColumnBriefDto.java b/fda-metadata-db/api/src/main/java/at/tuwien/api/database/table/columns/ColumnBriefDto.java index 2dbfcf406d526d5a88c03f5d98633683202a104c..7a09636bab38f9d00b8326d7f3df3e3e7fb72b81 100644 --- a/fda-metadata-db/api/src/main/java/at/tuwien/api/database/table/columns/ColumnBriefDto.java +++ b/fda-metadata-db/api/src/main/java/at/tuwien/api/database/table/columns/ColumnBriefDto.java @@ -28,4 +28,9 @@ public class ColumnBriefDto { @Parameter(name = "internal name", example = "mdb_date", required = true) private String internalName; + @NotNull + @JsonProperty("column_type") + @Parameter(name = "type", required = true) + private ColumnTypeDto columnType; + } diff --git a/fda-metadata-db/pom.xml b/fda-metadata-db/pom.xml index 994a0a0c0cf4907c1e1fd9c57dd59b81d59f767c..850a9b0a757f0ca25e4ad61f963e4fbdebef322d 100644 --- a/fda-metadata-db/pom.xml +++ b/fda-metadata-db/pom.xml @@ -18,6 +18,7 @@ <modules> <module>api</module> <module>entities</module> + <module>querystore</module> </modules> <properties> diff --git a/fda-metadata-db/querystore/pom.xml b/fda-metadata-db/querystore/pom.xml new file mode 100644 index 0000000000000000000000000000000000000000..c14e00e345b4aded4a1cd7403212bc5403d5ea6d --- /dev/null +++ b/fda-metadata-db/querystore/pom.xml @@ -0,0 +1,18 @@ +<?xml version="1.0" encoding="UTF-8"?> +<project xmlns="http://maven.apache.org/POM/4.0.0" + xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> + <modelVersion>4.0.0</modelVersion> + <parent> + <groupId>at.tuwien</groupId> + <artifactId>fda-metadata-db</artifactId> + <version>1.1.0-alpha</version> + </parent> + + <artifactId>fda-metadata-db-querystore</artifactId> + <version>1.1.0-alpha</version> + <name>fda-metadata-db-querystore</name> + + <dependencies/> + +</project> \ No newline at end of file diff --git a/fda-query-service/services/src/main/java/at/tuwien/querystore/Column.java b/fda-metadata-db/querystore/src/main/java/at/tuwien/querystore/Column.java similarity index 100% rename from fda-query-service/services/src/main/java/at/tuwien/querystore/Column.java rename to fda-metadata-db/querystore/src/main/java/at/tuwien/querystore/Column.java diff --git a/fda-query-service/services/src/main/java/at/tuwien/querystore/Query.java b/fda-metadata-db/querystore/src/main/java/at/tuwien/querystore/Query.java similarity index 96% rename from fda-query-service/services/src/main/java/at/tuwien/querystore/Query.java rename to fda-metadata-db/querystore/src/main/java/at/tuwien/querystore/Query.java index f662c81d6b01f9dac2bd18c80ca5bc6acb6fed54..796dfaee89082ffeaa6a23a1046a736a176f6479 100644 --- a/fda-query-service/services/src/main/java/at/tuwien/querystore/Query.java +++ b/fda-metadata-db/querystore/src/main/java/at/tuwien/querystore/Query.java @@ -7,6 +7,7 @@ import org.springframework.data.annotation.LastModifiedDate; import org.springframework.data.jpa.domain.support.AuditingEntityListener; import javax.persistence.*; +import javax.persistence.Table; import java.io.Serializable; import java.math.BigInteger; import java.time.Instant; @@ -64,7 +65,7 @@ public class Query implements Serializable { private Long createdBy; @OneToMany(fetch = FetchType.LAZY, cascade = CascadeType.MERGE) - private List<Table> tables; + private List<at.tuwien.querystore.Table> tables; @javax.persistence.Column(name = "last_modified") @LastModifiedDate diff --git a/fda-query-service/services/src/main/java/at/tuwien/querystore/Table.java b/fda-metadata-db/querystore/src/main/java/at/tuwien/querystore/Table.java similarity index 93% rename from fda-query-service/services/src/main/java/at/tuwien/querystore/Table.java rename to fda-metadata-db/querystore/src/main/java/at/tuwien/querystore/Table.java index ee707cc8c115d087ad7f4914072640d8b28fa0b1..c4628e1185ebbae3eb3cb39d6c45644853668f85 100644 --- a/fda-query-service/services/src/main/java/at/tuwien/querystore/Table.java +++ b/fda-metadata-db/querystore/src/main/java/at/tuwien/querystore/Table.java @@ -7,6 +7,7 @@ import org.springframework.data.annotation.LastModifiedDate; import org.springframework.data.jpa.domain.support.AuditingEntityListener; import javax.persistence.*; +import javax.persistence.Column; import java.io.Serializable; import java.time.Instant; import java.util.List; @@ -35,7 +36,7 @@ public class Table implements Serializable { private Long dbid; @OneToMany(fetch = FetchType.LAZY, cascade = CascadeType.MERGE) - private List<Column> columns; + private List<at.tuwien.querystore.Column> columns; @javax.persistence.Column(nullable = false, updatable = false) @CreatedDate diff --git a/fda-query-service/pom.xml b/fda-query-service/pom.xml index 64308c48222d924c175be0d799340fe8f0eccccb..f5cd96d75d1c927cd8a4aa456e691658478c7be0 100644 --- a/fda-query-service/pom.xml +++ b/fda-query-service/pom.xml @@ -132,7 +132,7 @@ <artifactId>tomcat-dbcp</artifactId> <version>9.0.33</version> </dependency> - <!-- Entity and API --> + <!-- Entity, API, QueryStore --> <dependency> <groupId>at.tuwien</groupId> <artifactId>fda-metadata-db-api</artifactId> @@ -145,6 +145,12 @@ <version>${project.version}</version> <scope>compile</scope> </dependency> + <dependency> + <groupId>at.tuwien</groupId> + <artifactId>fda-metadata-db-querystore</artifactId> + <version>${project.version}</version> + <scope>compile</scope> + </dependency> <dependency> <groupId>org.hibernate</groupId> <artifactId>hibernate-c3p0</artifactId> @@ -211,15 +217,6 @@ </dependencies> <build> - <resources> - <resource> - <directory>${basedir}/src/main/resources</directory> - <filtering>true</filtering> - <includes> - <include>**/application*.yml</include> - </includes> - </resource> - </resources> <plugins> <plugin> <groupId>org.jacoco</groupId> diff --git a/fda-query-service/rest-service/src/main/java/at/tuwien/endpoint/AbstractEndpoint.java b/fda-query-service/rest-service/src/main/java/at/tuwien/endpoint/AbstractEndpoint.java index bb7c46e45234bb9fd114bad4f75fe037d7730e25..65a1b63b168d1df492debd2b7f627e4fabe05bb6 100644 --- a/fda-query-service/rest-service/src/main/java/at/tuwien/endpoint/AbstractEndpoint.java +++ b/fda-query-service/rest-service/src/main/java/at/tuwien/endpoint/AbstractEndpoint.java @@ -1,14 +1,11 @@ package at.tuwien.endpoint; import at.tuwien.entities.database.Database; -import at.tuwien.entities.database.table.Table; import at.tuwien.entities.identifier.Identifier; import at.tuwien.exception.DatabaseNotFoundException; import at.tuwien.exception.IdentifierNotFoundException; -import at.tuwien.exception.TableNotFoundException; import at.tuwien.service.DatabaseService; import at.tuwien.service.IdentifierService; -import at.tuwien.service.TableService; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.security.core.Authentication; @@ -21,36 +18,30 @@ import static at.tuwien.entities.identifier.VisibilityType.EVERYONE; @Slf4j public abstract class AbstractEndpoint { - private final TableService tableService; private final DatabaseService databaseService; private final IdentifierService identifierService; @Autowired - protected AbstractEndpoint(TableService tableService, DatabaseService databaseService, - IdentifierService identifierService) { - this.tableService = tableService; + protected AbstractEndpoint(DatabaseService databaseService, IdentifierService identifierService) { this.databaseService = databaseService; this.identifierService = identifierService; } - protected Boolean hasDatabasePermission(Long containerId, Long databaseId, Long tableId, String permissionCode, + protected Boolean hasDatabasePermission(Long containerId, Long databaseId, String permissionCode, Principal principal) { - final Table table; + final Database database; try { - table = tableService.find(containerId, databaseId, tableId); + database = databaseService.find(containerId, databaseId); } catch (DatabaseNotFoundException e) { log.debug("failed to find database with id {}", databaseId); return false; - } catch (TableNotFoundException e) { - log.debug("failed to find table with id {} in database with id {}", tableId, databaseId); - return false; } - if (principal != null && table.getDatabase().getCreator().getUsername().equals(principal.getName())) { + if (principal != null && database.getCreator().getUsername().equals(principal.getName())) { log.debug("grant permission {} because user is creator of database with id {}", permissionCode, databaseId); return true; } /* view-only operations are allowed on public databases */ - if (table.getDatabase().getIsPublic() && List.of("DATA_EXPORT", "DATA_VIEW").contains(permissionCode)) { + if (database.getIsPublic() && List.of("DATA_EXPORT", "DATA_VIEW", "DATA_HISTORY").contains(permissionCode)) { log.debug("grant permission {} because database is public", permissionCode); return true; } @@ -69,43 +60,10 @@ public abstract class AbstractEndpoint { return true; } - protected Boolean hasQueryPermission(Long databaseId, String permissionCode, Principal principal) { + protected Boolean hasQueryPermission(Long containerId, Long databaseId, Long queryId, String permissionCode, Principal principal) { final Database database; try { - database = databaseService.find(databaseId); - } catch (DatabaseNotFoundException e) { - log.debug("failed to find database with id {}", databaseId); - return false; - } - if (database.getIsPublic()) { - log.debug("grant permission {} because database is public", permissionCode); - return true; - } - if (principal == null) { - log.debug("failed to grant permission {} because principal is null", permissionCode); - return false; - } - final Authentication authentication = (Authentication) principal /* with pre-authorization this always holds */; - if (authentication.getAuthorities().stream().noneMatch(a -> a.getAuthority().equals("ROLE_RESEARCHER"))) { - log.debug("failed to grant permission {} because current user misses authority 'ROLE_RESEARCHER'", - permissionCode); - return false; - } - /* modification operations are limited to the creator */ - if (database.getCreator().getUsername().equals(principal.getName())) { - log.debug("grant permission {} because database is private and creator is the current user", - permissionCode); - return true; - } - log.debug("failed to grant permission {} because database is private and creator is not the " + - "current user", permissionCode); - return false; - } - - protected Boolean hasQueryPermission(Long databaseId, Long queryId, String permissionCode, Principal principal) { - final Database database; - try { - database = databaseService.find(databaseId); + database = databaseService.find(containerId, databaseId); } catch (DatabaseNotFoundException e) { log.debug("failed to find database with id {}", databaseId); return false; @@ -149,7 +107,6 @@ public abstract class AbstractEndpoint { try { identifier = identifierService.findByDatabaseIdAndQueryId(databaseId, queryId); } catch (IdentifierNotFoundException e) { - log.debug("failed to find identifier with database id {} and query id {}", databaseId, queryId); return false; } if (identifier.getVisibility().equals(EVERYONE)) { @@ -165,7 +122,6 @@ public abstract class AbstractEndpoint { try { identifier = identifierService.findByDatabaseIdAndQueryId(databaseId, queryId); } catch (IdentifierNotFoundException e) { - log.debug("failed to find identifier with database id {} and query id {}", databaseId, queryId); return false; } if (identifier.getDatabase().getIsPublic()) { @@ -183,7 +139,7 @@ public abstract class AbstractEndpoint { return true; } log.debug("failed to grant permission {} because database is private and identifier creator is not the " + - "current user", permissionCode); + "current user", permissionCode); return false; } diff --git a/fda-query-service/rest-service/src/main/java/at/tuwien/endpoint/ExportEndpoint.java b/fda-query-service/rest-service/src/main/java/at/tuwien/endpoint/ExportEndpoint.java index 2db195215171969ddeaf6597bb4f7a67f585b30b..8503ed53d16f0f837a8759afbc953f69a36e9eae 100644 --- a/fda-query-service/rest-service/src/main/java/at/tuwien/endpoint/ExportEndpoint.java +++ b/fda-query-service/rest-service/src/main/java/at/tuwien/endpoint/ExportEndpoint.java @@ -5,7 +5,6 @@ import at.tuwien.exception.*; import at.tuwien.service.DatabaseService; import at.tuwien.service.IdentifierService; import at.tuwien.service.QueryService; -import at.tuwien.service.TableService; import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.security.SecurityRequirement; import lombok.extern.log4j.Log4j2; @@ -29,9 +28,9 @@ public class ExportEndpoint extends AbstractEndpoint { private final QueryService queryService; @Autowired - public ExportEndpoint(TableService tableService, QueryService queryService, DatabaseService databaseService, + public ExportEndpoint(QueryService queryService, DatabaseService databaseService, IdentifierService identifierService) { - super(tableService, databaseService, identifierService); + super(databaseService, identifierService); this.queryService = queryService; } @@ -46,7 +45,7 @@ public class ExportEndpoint extends AbstractEndpoint { throws TableNotFoundException, DatabaseConnectionException, TableMalformedException, DatabaseNotFoundException, ImageNotSupportedException, PaginationException, ContainerNotFoundException, FileStorageException, NotAllowedException { - if (!hasDatabasePermission(id, databaseId, tableId, "DATA_EXPORT", principal)) { + if (!hasDatabasePermission(id, databaseId, "DATA_EXPORT", principal)) { log.error("Missing data export permission"); throw new NotAllowedException("Missing data export permission"); } diff --git a/fda-query-service/rest-service/src/main/java/at/tuwien/endpoint/QueryEndpoint.java b/fda-query-service/rest-service/src/main/java/at/tuwien/endpoint/QueryEndpoint.java index 118d5ae77bca603f830b8102af73e2fcddbe3a34..02b340f324f0a923c7f8ce1524c128ef91085001 100644 --- a/fda-query-service/rest-service/src/main/java/at/tuwien/endpoint/QueryEndpoint.java +++ b/fda-query-service/rest-service/src/main/java/at/tuwien/endpoint/QueryEndpoint.java @@ -13,7 +13,6 @@ import org.springframework.core.io.InputStreamResource; import org.springframework.http.HttpHeaders; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; -import org.springframework.security.access.prepost.PreAuthorize; import org.springframework.transaction.annotation.Transactional; import org.springframework.web.bind.annotation.*; @@ -30,10 +29,10 @@ public class QueryEndpoint extends AbstractEndpoint { private final StoreService storeService; @Autowired - public QueryEndpoint(TableService tableService, QueryService queryService, StoreService storeService, + public QueryEndpoint(QueryService queryService, StoreService storeService, DatabaseService databaseService, IdentifierService identifierService) { - super(tableService, databaseService, identifierService); + super(databaseService, identifierService); this.queryService = queryService; this.storeService = storeService; } @@ -41,7 +40,7 @@ public class QueryEndpoint extends AbstractEndpoint { @PutMapping @Transactional(readOnly = true) @Operation(summary = "Execute query", security = @SecurityRequirement(name = "bearerAuth")) - public ResponseEntity<QueryResultDto> execute(@NotNull @PathVariable("id") Long id, + public ResponseEntity<QueryResultDto> execute(@NotNull @PathVariable("id") Long containerId, @NotNull @PathVariable("databaseId") Long databaseId, @NotNull @Valid @RequestBody ExecuteStatementDto data, @RequestParam(value = "page", required = false) Long page, @@ -50,7 +49,7 @@ public class QueryEndpoint extends AbstractEndpoint { throws DatabaseNotFoundException, ImageNotSupportedException, QueryStoreException, QueryMalformedException, ContainerNotFoundException, ColumnParseException, UserNotFoundException, TableMalformedException, NotAllowedException { - if (!hasQueryPermission(databaseId, "QUERY_EXECUTE", principal)) { + if (!hasDatabasePermission(containerId, databaseId, "QUERY_EXECUTE", principal)) { log.error("Missing execute query permission"); throw new NotAllowedException("Missing execute query permission"); } @@ -59,8 +58,7 @@ public class QueryEndpoint extends AbstractEndpoint { log.error("Query is empty"); throw new QueryMalformedException("Query is empty"); } - log.trace("data for execution: {}", data); - final QueryResultDto result = queryService.execute(id, databaseId, data, principal, page, size); + final QueryResultDto result = queryService.execute(containerId, databaseId, data, principal, page, size); return ResponseEntity.status(HttpStatus.ACCEPTED) .body(result); } @@ -68,7 +66,7 @@ public class QueryEndpoint extends AbstractEndpoint { @PutMapping("/{queryId}") @Transactional(readOnly = true) @Operation(summary = "Re-execute some query") - public ResponseEntity<QueryResultDto> reExecute(@NotNull @PathVariable("id") Long id, + public ResponseEntity<QueryResultDto> reExecute(@NotNull @PathVariable("id") Long containerId, @NotNull @PathVariable("databaseId") Long databaseId, @NotNull @PathVariable("queryId") Long queryId, @RequestParam(value = "page", required = false) Long page, @@ -77,12 +75,12 @@ public class QueryEndpoint extends AbstractEndpoint { throws QueryStoreException, QueryNotFoundException, DatabaseNotFoundException, ImageNotSupportedException, TableNotFoundException, QueryMalformedException, ContainerNotFoundException, TableMalformedException, ColumnParseException, NotAllowedException { - if (!hasQueryPermission(databaseId, queryId, "QUERY_RE_EXECUTE", principal)) { + if (!hasQueryPermission(containerId, databaseId, queryId, "QUERY_RE_EXECUTE", principal)) { log.error("Missing re-execute query permission"); throw new NotAllowedException("Missing re-execute query permission"); } - final Query query = storeService.findOne(id, databaseId, queryId); - final QueryResultDto result = queryService.reExecute(id, databaseId, query, page, size); + final Query query = storeService.findOne(containerId, databaseId, queryId); + final QueryResultDto result = queryService.reExecute(containerId, databaseId, query, page, size); result.setId(queryId); return ResponseEntity.status(HttpStatus.ACCEPTED) .body(result); @@ -91,19 +89,19 @@ public class QueryEndpoint extends AbstractEndpoint { @GetMapping("/{queryId}/export") @Transactional(readOnly = true) @Operation(summary = "Exports some query", security = @SecurityRequirement(name = "bearerAuth")) - public ResponseEntity<InputStreamResource> export(@NotNull @PathVariable("id") Long id, + public ResponseEntity<InputStreamResource> export(@NotNull @PathVariable("id") Long containerId, @NotNull @PathVariable("databaseId") Long databaseId, @NotNull @PathVariable("queryId") Long queryId, @NotNull Principal principal) throws QueryStoreException, QueryNotFoundException, DatabaseNotFoundException, ImageNotSupportedException, ContainerNotFoundException, TableMalformedException, FileStorageException, NotAllowedException, QueryMalformedException { - if (!hasQueryPermission(databaseId, queryId, "QUERY_EXPORT", principal)) { + if (!hasQueryPermission(containerId, databaseId, queryId, "QUERY_EXPORT", principal)) { log.error("Missing export query permission"); throw new NotAllowedException("Missing export query permission"); } - storeService.findOne(id, databaseId, queryId); + storeService.findOne(containerId, databaseId, queryId); final HttpHeaders headers = new HttpHeaders(); - final ExportResource resource = queryService.findOne(id, databaseId, queryId); + final ExportResource resource = queryService.findOne(containerId, databaseId, queryId); headers.add("Content-Disposition", "attachment; filename=\"" + resource.getFilename() + "\""); return ResponseEntity.ok() .headers(headers) diff --git a/fda-query-service/rest-service/src/main/java/at/tuwien/endpoint/StoreEndpoint.java b/fda-query-service/rest-service/src/main/java/at/tuwien/endpoint/StoreEndpoint.java index 15c88b273b27630748590d309c8e896e8f9021cb..72482b9bcb0e7eab7acb9c03fe26110cc5de008b 100644 --- a/fda-query-service/rest-service/src/main/java/at/tuwien/endpoint/StoreEndpoint.java +++ b/fda-query-service/rest-service/src/main/java/at/tuwien/endpoint/StoreEndpoint.java @@ -12,7 +12,6 @@ import io.swagger.v3.oas.annotations.security.SecurityRequirement; import lombok.extern.log4j.Log4j2; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.http.ResponseEntity; -import org.springframework.security.access.prepost.PreAuthorize; import org.springframework.transaction.annotation.Transactional; import org.springframework.web.bind.annotation.*; @@ -31,10 +30,10 @@ public class StoreEndpoint extends AbstractEndpoint { private final StoreService storeService; @Autowired - public StoreEndpoint(TableService tableService, UserMapper userMapper, QueryMapper queryMapper, + public StoreEndpoint(UserMapper userMapper, QueryMapper queryMapper, UserService userService, StoreService storeService, DatabaseService databaseService, IdentifierService identifierService) { - super(tableService, databaseService, identifierService); + super(databaseService, identifierService); this.userMapper = userMapper; this.queryMapper = queryMapper; this.userService = userService; @@ -43,36 +42,35 @@ public class StoreEndpoint extends AbstractEndpoint { @GetMapping @Transactional(readOnly = true) - @PreAuthorize("hasPermission(#databaseId, 'QUERY_VIEW_ALL')") @Operation(summary = "Find queries", security = @SecurityRequirement(name = "bearerAuth")) - public ResponseEntity<List<QueryDto>> findAll(@NotNull @PathVariable("id") Long id, + public ResponseEntity<List<QueryDto>> findAll(@NotNull @PathVariable("id") Long containerId, @NotNull @PathVariable("databaseId") Long databaseId, @NotNull Principal principal) throws QueryStoreException, DatabaseNotFoundException, ImageNotSupportedException, ContainerNotFoundException, NotAllowedException { - if (!hasQueryPermission(databaseId, null, "QUERY_VIEW_ALL", principal)) { + if (!hasDatabasePermission(containerId, databaseId, "QUERY_VIEW_ALL", principal)) { log.error("Missing view all queries permission"); throw new NotAllowedException("Missing view all queries permission"); } - final List<Query> queries = storeService.findAll(id, databaseId); + final List<Query> queries = storeService.findAll(containerId, databaseId); return ResponseEntity.ok(queryMapper.queryListToQueryDtoList(queries)); } @GetMapping("/{queryId}") @Transactional(readOnly = true) @Operation(summary = "Find some query", security = @SecurityRequirement(name = "bearerAuth")) - public ResponseEntity<QueryDto> find(@NotNull @PathVariable("id") Long id, + public ResponseEntity<QueryDto> find(@NotNull @PathVariable("id") Long containerId, @NotNull @PathVariable("databaseId") Long databaseId, @NotNull @PathVariable Long queryId, @NotNull Principal principal) throws DatabaseNotFoundException, ImageNotSupportedException, QueryStoreException, QueryNotFoundException, ContainerNotFoundException, UserNotFoundException, NotAllowedException { - if (!hasQueryPermission(databaseId, queryId, "QUERY_VIEW", principal)) { + if (!hasQueryPermission(containerId, databaseId, queryId, "QUERY_VIEW", principal)) { log.error("Missing view query permission"); throw new NotAllowedException("Missing view query permission"); } - final Query query = storeService.findOne(id, databaseId, queryId); + final Query query = storeService.findOne(containerId, databaseId, queryId); final QueryDto dto = queryMapper.queryToQueryDto(query); final User creator = userService.find(query.getCreatedBy()); dto.setCreator(userMapper.userToUserDto(creator)); diff --git a/fda-query-service/rest-service/src/main/java/at/tuwien/endpoint/TableDataEndpoint.java b/fda-query-service/rest-service/src/main/java/at/tuwien/endpoint/TableDataEndpoint.java index 31615d4a4f6ccf4025b88e73172b295749d7ce9f..04833a1abf328354ae5c0e2fd2b3676f780eb4ac 100644 --- a/fda-query-service/rest-service/src/main/java/at/tuwien/endpoint/TableDataEndpoint.java +++ b/fda-query-service/rest-service/src/main/java/at/tuwien/endpoint/TableDataEndpoint.java @@ -29,15 +29,12 @@ import java.time.Instant; public class TableDataEndpoint extends AbstractEndpoint { private final QueryService queryService; - private final StoreService storeService; @Autowired - public TableDataEndpoint(TableService tableService, QueryService queryService, StoreService storeService, - DatabaseService databaseService, + public TableDataEndpoint(QueryService queryService, DatabaseService databaseService, IdentifierService identifierService) { - super(tableService, databaseService, identifierService); + super(databaseService, identifierService); this.queryService = queryService; - this.storeService = storeService; } @PostMapping @@ -50,7 +47,7 @@ public class TableDataEndpoint extends AbstractEndpoint { @NotNull Principal principal) throws TableNotFoundException, DatabaseNotFoundException, TableMalformedException, ImageNotSupportedException, ContainerNotFoundException, NotAllowedException { - if (!hasDatabasePermission(containerId, databaseId, tableId, "DATA_INSERT", principal)) { + if (!hasDatabasePermission(containerId, databaseId, "DATA_INSERT", principal)) { log.error("Missing data insert permission"); throw new NotAllowedException("Missing data insert permission"); } @@ -67,8 +64,8 @@ public class TableDataEndpoint extends AbstractEndpoint { @NotNull @Valid @RequestBody TableCsvUpdateDto data, @NotNull Principal principal) throws TableNotFoundException, DatabaseNotFoundException, TableMalformedException, - ImageNotSupportedException, NotAllowedException { - if (!hasDatabasePermission(containerId, databaseId, tableId, "DATA_UPDATE", principal)) { + ImageNotSupportedException, NotAllowedException, ContainerNotFoundException { + if (!hasDatabasePermission(containerId, databaseId, "DATA_UPDATE", principal)) { log.error("Missing data update permission"); throw new NotAllowedException("Missing data update permission"); } @@ -85,8 +82,8 @@ public class TableDataEndpoint extends AbstractEndpoint { @NotNull @Valid @RequestBody TableCsvDeleteDto data, @NotNull Principal principal) throws TableNotFoundException, DatabaseNotFoundException, TableMalformedException, - ImageNotSupportedException, TupleDeleteException, NotAllowedException { - if (!hasDatabasePermission(containerId, databaseId, tableId, "DATA_DELETE", principal)) { + ImageNotSupportedException, TupleDeleteException, NotAllowedException, ContainerNotFoundException { + if (!hasDatabasePermission(containerId, databaseId, "DATA_DELETE", principal)) { log.error("Missing data delete permission"); throw new NotAllowedException("Missing data delete permission"); } @@ -105,7 +102,7 @@ public class TableDataEndpoint extends AbstractEndpoint { @NotNull Principal principal) throws TableNotFoundException, DatabaseNotFoundException, TableMalformedException, ImageNotSupportedException, ContainerNotFoundException, NotAllowedException { - if (!hasDatabasePermission(containerId, databaseId, tableId, "DATA_INSERT", principal)) { + if (!hasDatabasePermission(containerId, databaseId, "DATA_INSERT", principal)) { log.error("Missing data insert permission"); throw new NotAllowedException("Missing data insert permission"); } @@ -127,7 +124,7 @@ public class TableDataEndpoint extends AbstractEndpoint { throws TableNotFoundException, DatabaseNotFoundException, DatabaseConnectionException, ImageNotSupportedException, TableMalformedException, PaginationException, ContainerNotFoundException, QueryStoreException, NotAllowedException { - if (!hasDatabasePermission(containerId, databaseId, tableId, "DATA_VIEW", principal)) { + if (!hasDatabasePermission(containerId, databaseId, "DATA_VIEW", principal)) { log.error("Missing data view permission"); throw new NotAllowedException("Missing data view permission"); } @@ -143,8 +140,6 @@ public class TableDataEndpoint extends AbstractEndpoint { if (size != null && size <= 0) { throw new PaginationException("Page number cannot be lower or equal to 0"); } - /* fixme query store maybe not created, create it through running findAll() */ - storeService.findAll(containerId, databaseId); final BigInteger count = queryService.count(containerId, databaseId, tableId, timestamp); final HttpHeaders headers = new HttpHeaders(); headers.set("FDA-COUNT", count.toString()); diff --git a/fda-query-service/rest-service/src/main/java/at/tuwien/endpoint/TableHistoryEndpoint.java b/fda-query-service/rest-service/src/main/java/at/tuwien/endpoint/TableHistoryEndpoint.java index dd65407cee41de3682c4e08629ba6f29545dd9d5..b214dee04c2590d61c134c4ac3bf2312bc415f5f 100644 --- a/fda-query-service/rest-service/src/main/java/at/tuwien/endpoint/TableHistoryEndpoint.java +++ b/fda-query-service/rest-service/src/main/java/at/tuwien/endpoint/TableHistoryEndpoint.java @@ -2,9 +2,10 @@ package at.tuwien.endpoint; import at.tuwien.api.database.table.TableHistoryDto; import at.tuwien.exception.DatabaseNotFoundException; +import at.tuwien.exception.NotAllowedException; import at.tuwien.exception.QueryMalformedException; import at.tuwien.exception.TableNotFoundException; -import at.tuwien.service.TableService; +import at.tuwien.service.*; import io.swagger.v3.oas.annotations.Operation; import lombok.extern.log4j.Log4j2; import org.springframework.beans.factory.annotation.Autowired; @@ -13,18 +14,21 @@ import org.springframework.transaction.annotation.Transactional; import org.springframework.web.bind.annotation.*; import javax.validation.constraints.NotNull; +import java.security.Principal; import java.util.List; @Log4j2 @CrossOrigin(origins = "*") @RestController @RequestMapping("/api/container/{id}/database/{databaseId}/table/{tableId}/history") -public class TableHistoryEndpoint { +public class TableHistoryEndpoint extends AbstractEndpoint { private final TableService tableService; @Autowired - public TableHistoryEndpoint(TableService tableService) { + public TableHistoryEndpoint(TableService tableService, DatabaseService databaseService, + IdentifierService identifierService) { + super(databaseService, identifierService); this.tableService = tableService; } @@ -33,8 +37,13 @@ public class TableHistoryEndpoint { @Operation(summary = "Find all history") public ResponseEntity<List<TableHistoryDto>> getAll(@NotNull @PathVariable("id") Long containerId, @NotNull @PathVariable("databaseId") Long databaseId, - @NotNull @PathVariable("tableId") Long tableId) - throws TableNotFoundException, QueryMalformedException, DatabaseNotFoundException { + @NotNull @PathVariable("tableId") Long tableId, + @NotNull Principal principal) + throws TableNotFoundException, QueryMalformedException, DatabaseNotFoundException, NotAllowedException { + if (!hasDatabasePermission(containerId, databaseId, "DATA_HISTORY", principal)) { + log.error("Missing data history permission"); + throw new NotAllowedException("Missing data history permission"); + } final List<TableHistoryDto> history = tableService.findHistory(containerId, databaseId, tableId); return ResponseEntity.ok(history); } diff --git a/fda-query-service/rest-service/src/main/resources/mariadb_hibernate.cfg.xml b/fda-query-service/rest-service/src/main/resources/mariadb_hibernate.cfg.xml new file mode 100644 index 0000000000000000000000000000000000000000..01f90448caca030263dade2fe9e4d07c20b938f0 --- /dev/null +++ b/fda-query-service/rest-service/src/main/resources/mariadb_hibernate.cfg.xml @@ -0,0 +1,20 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!DOCTYPE hibernate-configuration PUBLIC + "-//Hibernate/Hibernate Configuration DTD 3.0//EN" + "http://www.hibernate.org/dtd/hibernate-configuration-3.0.dtd"> +<hibernate-configuration> + <session-factory> + <property name="current_session_context_class">thread</property> + <property name="transaction.coordinator_class">jdbc</property> + <property name="c3p0.min_size">1</property> + <property name="c3p0.max_size">30</property> + <property name="c3p0.acquire_increment">1</property> + <property name="c3p0.timeout">1800</property> + <property name="show_sql">true</property> + <property name="format_sql">true</property> + <property name="hbm2ddl.auto">update</property> + <mapping class="at.tuwien.querystore.Column" /> + <mapping class="at.tuwien.querystore.Query" /> + <mapping class="at.tuwien.querystore.Table" /> + </session-factory> +</hibernate-configuration> diff --git a/fda-query-service/rest-service/src/test/java/at/tuwien/endpoint/StoreEndpointUnitTest.java b/fda-query-service/rest-service/src/test/java/at/tuwien/endpoint/StoreEndpointUnitTest.java index c31c67f5684c8e646f7079a6032645e53ae09b5b..8b6df814fb489d86889a70b038619466a107242f 100644 --- a/fda-query-service/rest-service/src/test/java/at/tuwien/endpoint/StoreEndpointUnitTest.java +++ b/fda-query-service/rest-service/src/test/java/at/tuwien/endpoint/StoreEndpointUnitTest.java @@ -74,7 +74,7 @@ public class StoreEndpointUnitTest extends BaseUnitTest { @Test public void find_notFound_fails() throws QueryNotFoundException, DatabaseNotFoundException, - ImageNotSupportedException, ContainerNotFoundException { + ImageNotSupportedException, ContainerNotFoundException, QueryStoreException { final Principal principal = new BasicUserPrincipal(USER_1_USERNAME); /* mock */ @@ -89,7 +89,7 @@ public class StoreEndpointUnitTest extends BaseUnitTest { @Test public void find_dbNotFound_fails() throws QueryNotFoundException, DatabaseNotFoundException, - ImageNotSupportedException, ContainerNotFoundException { + ImageNotSupportedException, ContainerNotFoundException, QueryStoreException { final Principal principal = new BasicUserPrincipal(USER_1_USERNAME); /* mock */ diff --git a/fda-query-service/rest-service/src/test/java/at/tuwien/service/DataServiceIntegrationTest.java b/fda-query-service/rest-service/src/test/java/at/tuwien/service/DataServiceIntegrationTest.java index f8e0de41067cbe3ae3e0475248acaf73edd5cdaf..56e2964fe3ca33d797c4185709d719c98a3ff089 100644 --- a/fda-query-service/rest-service/src/test/java/at/tuwien/service/DataServiceIntegrationTest.java +++ b/fda-query-service/rest-service/src/test/java/at/tuwien/service/DataServiceIntegrationTest.java @@ -5,10 +5,7 @@ import at.tuwien.api.database.table.TableCsvUpdateDto; import at.tuwien.config.DockerConfig; import at.tuwien.config.MariaDbConfig; import at.tuwien.config.ReadyConfig; -import at.tuwien.exception.DatabaseNotFoundException; -import at.tuwien.exception.ImageNotSupportedException; -import at.tuwien.exception.TableMalformedException; -import at.tuwien.exception.TableNotFoundException; +import at.tuwien.exception.*; import at.tuwien.repository.jpa.TableRepository; import com.github.dockerjava.api.command.CreateContainerResponse; import com.github.dockerjava.api.exception.NotModifiedException; @@ -118,7 +115,7 @@ public class DataServiceIntegrationTest extends BaseUnitTest { } public void update_succeeds() throws TableNotFoundException, TableMalformedException, DatabaseNotFoundException, - ImageNotSupportedException, SQLException { + ImageNotSupportedException, SQLException, ContainerNotFoundException { /* modify rainfall 0.6 -> 1.3 */ final TableCsvUpdateDto request = TableCsvUpdateDto.builder() .keys(Map.ofEntries(Map.entry("id", 1))) diff --git a/fda-query-service/services/src/main/java/at/tuwien/auth/UserPermissionEvaluator.java b/fda-query-service/services/src/main/java/at/tuwien/auth/UserPermissionEvaluator.java deleted file mode 100644 index fac8cd24b9a3128e7824460268debf91394ecb3f..0000000000000000000000000000000000000000 --- a/fda-query-service/services/src/main/java/at/tuwien/auth/UserPermissionEvaluator.java +++ /dev/null @@ -1,86 +0,0 @@ -package at.tuwien.auth; - -import at.tuwien.api.user.UserDetailsDto; -import at.tuwien.entities.database.Database; -import at.tuwien.exception.DatabaseNotFoundException; -import at.tuwien.service.DatabaseService; -import lombok.extern.slf4j.Slf4j; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.security.access.PermissionEvaluator; -import org.springframework.security.core.Authentication; -import org.springframework.stereotype.Component; - -import java.io.Serializable; - -@Slf4j -@Component -public class UserPermissionEvaluator implements PermissionEvaluator { - - private final DatabaseService databaseService; - - @Autowired - public UserPermissionEvaluator(DatabaseService databaseService) { - this.databaseService = databaseService; - } - - @Override - public boolean hasPermission(Authentication auth, Object targetDomainObject, Object permission) { - log.trace("has permission auth {} target domain {} permission {}", auth, targetDomainObject, permission); - if (auth == null) { - log.error("Authentication principal is null"); - return false; - } - if (!(targetDomainObject instanceof Long)) { - log.error("Domain is not of type Long"); - return false; - } - if (!(permission instanceof String)) { - log.error("Permission is not of type String"); - return false; - } - log.trace("principal is {}", auth.getPrincipal()); - final UserDetailsDto principal; - if (!(auth.getPrincipal() instanceof UserDetailsDto) || auth.getPrincipal() == null) { - log.warn("Principal is null"); - principal = null; - } else { - principal = (UserDetailsDto) auth.getPrincipal(); - } - final Long targetDomainId = (Long) targetDomainObject; - final String permissionCode = (String) permission; - switch (permissionCode) { - case "QUERY_VIEW_ALL": - case "QUERY_EXECUTE": - case "QUERY_RE_EXECUTE": - case "QUERY_EXPORT": - case "QUERY_VIEW": - final Database database; - try { - database = databaseService.find(targetDomainId); - } catch (DatabaseNotFoundException e) { - log.error("Failed to find database with id {}", targetDomainId); - return false; - } - /* view-only operations are allowed on public databases */ - if (database.getIsPublic() && permissionCode.equals("QUERY_VIEW_ALL")) { - return true; - } - /* modification operations are limited to the creator */ - if (principal == null) { - return false; - } - if (principal.getAuthorities().stream().noneMatch(a -> a.getAuthority().equals("ROLE_RESEARCHER"))) { - log.error("Current user has insufficient authorities"); - log.debug("current user misses ROLE_RESEARCHER"); - return false; - } - return database.getCreator().getUsername().equals(principal.getUsername()); - } - return false; - } - - @Override - public boolean hasPermission(Authentication auth, Serializable targetId, String targetType, Object permission) { - return false; - } -} diff --git a/fda-query-service/services/src/main/java/at/tuwien/mapper/QueryMapper.java b/fda-query-service/services/src/main/java/at/tuwien/mapper/QueryMapper.java index 04b477670263eb844d2f05767bb798da5b4e464a..accebc2dee1abcb8a74c43291e97f614b77fe616 100644 --- a/fda-query-service/services/src/main/java/at/tuwien/mapper/QueryMapper.java +++ b/fda-query-service/services/src/main/java/at/tuwien/mapper/QueryMapper.java @@ -536,8 +536,9 @@ public interface QueryMapper { /* query check (this is enforced by the db also) */ final String query_ = query; if (Stream.of("delete", "update", "truncate", "create", "drop").anyMatch(query_::startsWith)) { - log.error("Query attempts to modify the database."); - throw new QueryMalformedException("Query attempts to modify the databse"); + log.error("Query attempts to modify the database"); + log.debug("query attempts to modify the database [{}]", query_); + throw new QueryMalformedException("Query attempts to modify the database"); } final StringBuilder sb = new StringBuilder(); if (!query.contains("where")) { diff --git a/fda-query-service/services/src/main/java/at/tuwien/repository/jpa/DatabaseRepository.java b/fda-query-service/services/src/main/java/at/tuwien/repository/jpa/DatabaseRepository.java index fcd76409bb50b89bc2c1f7709df6c650aa03196e..e73c2e7b2b4ef46bb683ffd847989749f50ffada 100644 --- a/fda-query-service/services/src/main/java/at/tuwien/repository/jpa/DatabaseRepository.java +++ b/fda-query-service/services/src/main/java/at/tuwien/repository/jpa/DatabaseRepository.java @@ -2,12 +2,17 @@ package at.tuwien.repository.jpa; import at.tuwien.entities.database.Database; import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.Query; +import org.springframework.data.repository.query.Param; import org.springframework.stereotype.Repository; +import java.util.Optional; + @Repository public interface DatabaseRepository extends JpaRepository<Database, Long> { - + @Query(value = "select d from Database d where d.container.id = :containerId and d.id = :databaseId") + Optional<Database> findByContainerIdAndDatabaseId(@Param("containerId") Long containerId, @Param("databaseId") Long databaseId); } diff --git a/fda-query-service/services/src/main/java/at/tuwien/service/DatabaseService.java b/fda-query-service/services/src/main/java/at/tuwien/service/DatabaseService.java index 2f0f265331c5d76c7b4cb146ce82b7445a7089aa..2e27dc0b4024fe4d03687eb77bde0df89dc3c374 100644 --- a/fda-query-service/services/src/main/java/at/tuwien/service/DatabaseService.java +++ b/fda-query-service/services/src/main/java/at/tuwien/service/DatabaseService.java @@ -8,9 +8,10 @@ public interface DatabaseService { /** * Finds a database by given id in the remote database service. * - * @param id The id. + * @param containerId The container id. + * @param databaseId The database id. * @return The database. * @throws DatabaseNotFoundException The database was not found. */ - Database find(Long id) throws DatabaseNotFoundException; + Database find(Long containerId, Long databaseId) throws DatabaseNotFoundException; } diff --git a/fda-query-service/services/src/main/java/at/tuwien/service/MessageQueueService.java b/fda-query-service/services/src/main/java/at/tuwien/service/MessageQueueService.java index 89822c2d0885abb143e3962df4e66d8a561e4b55..24a8e789ea4fcc11cb0d68b14e1f292be2874326 100644 --- a/fda-query-service/services/src/main/java/at/tuwien/service/MessageQueueService.java +++ b/fda-query-service/services/src/main/java/at/tuwien/service/MessageQueueService.java @@ -1,6 +1,8 @@ package at.tuwien.service; +import at.tuwien.exception.AmqpException; + public interface MessageQueueService { - void renewConsumers(); + void createConsumer(String routingKey, Long containerId, Long databaseId, Long tableId) throws AmqpException; } diff --git a/fda-query-service/services/src/main/java/at/tuwien/service/QueryService.java b/fda-query-service/services/src/main/java/at/tuwien/service/QueryService.java index 64cc05e1198b9dbd8a19dd8a2a8b78430c4a9d7e..a65bece0d59d5bbc24706a7d6184dcf52e7103c7 100644 --- a/fda-query-service/services/src/main/java/at/tuwien/service/QueryService.java +++ b/fda-query-service/services/src/main/java/at/tuwien/service/QueryService.java @@ -178,7 +178,7 @@ public interface QueryService { */ Integer update(Long containerId, Long databaseId, Long tableId, TableCsvUpdateDto data) throws ImageNotSupportedException, TableMalformedException, DatabaseNotFoundException, - TableNotFoundException; + TableNotFoundException, ContainerNotFoundException; /** * Deletes a tuple by given constraint set @@ -195,7 +195,7 @@ public interface QueryService { */ void delete(Long containerId, Long databaseId, Long tableId, TableCsvDeleteDto data) throws ImageNotSupportedException, TableMalformedException, DatabaseNotFoundException, - TableNotFoundException, TupleDeleteException; + TableNotFoundException, TupleDeleteException, ContainerNotFoundException; /** * Insert data from a csv into a table of a table-database id tuple, we need the "root" role for this as the diff --git a/fda-query-service/services/src/main/java/at/tuwien/service/impl/DatabaseServiceImpl.java b/fda-query-service/services/src/main/java/at/tuwien/service/impl/DatabaseServiceImpl.java index 4d0920ba0357b997d0662e9a15cce89c7cf4c785..fbbd821e47ff43f66260511986a659150807d7b7 100644 --- a/fda-query-service/services/src/main/java/at/tuwien/service/impl/DatabaseServiceImpl.java +++ b/fda-query-service/services/src/main/java/at/tuwien/service/impl/DatabaseServiceImpl.java @@ -24,11 +24,12 @@ public class DatabaseServiceImpl implements DatabaseService { @Override @Transactional - public Database find(Long id) throws DatabaseNotFoundException { - final Optional<Database> database = databaseRepository.findById(id); + public Database find(Long containerId, Long databaseId) throws DatabaseNotFoundException { + final Optional<Database> database = databaseRepository.findByContainerIdAndDatabaseId(containerId, databaseId); if (database.isEmpty()) { - log.error("Database with id {} not found in metadata database", id); - throw new DatabaseNotFoundException("Database not found in metadata database"); + log.error("Failed to find database"); + log.debug("failed to find database with container id {} and database id {}", containerId, databaseId); + throw new DatabaseNotFoundException("Failed to find database"); } return database.get(); } diff --git a/fda-query-service/services/src/main/java/at/tuwien/service/impl/HibernateConnector.java b/fda-query-service/services/src/main/java/at/tuwien/service/impl/HibernateConnector.java index fe737da95ef604c082a635fe4a213fe15742735c..6538e8cfb6068397d464dea5a3626aa59ed004dd 100644 --- a/fda-query-service/services/src/main/java/at/tuwien/service/impl/HibernateConnector.java +++ b/fda-query-service/services/src/main/java/at/tuwien/service/impl/HibernateConnector.java @@ -1,89 +1,70 @@ package at.tuwien.service.impl; +import at.tuwien.entities.container.Container; +import at.tuwien.entities.container.image.ContainerImage; import at.tuwien.entities.container.image.ContainerImageEnvironmentItem; import at.tuwien.entities.container.image.ContainerImageEnvironmentItemType; import at.tuwien.entities.database.Database; -import at.tuwien.querystore.Column; -import at.tuwien.querystore.Query; -import at.tuwien.querystore.Table; import lombok.extern.log4j.Log4j2; import org.hibernate.Session; import org.hibernate.SessionFactory; +import org.hibernate.Transaction; import org.hibernate.cfg.Configuration; +import org.hibernate.query.NativeQuery; import org.springframework.stereotype.Service; -import org.springframework.transaction.annotation.Transactional; +import javax.persistence.PersistenceException; +import java.util.List; import java.util.stream.Collectors; @Log4j2 @Service public abstract class HibernateConnector { - private static final Integer MIN_SIZE = 5; - private static final Integer MAX_SIZE = 500; - private static final Integer INCREMENT_SIZE = 5; - private static final Integer TIMEOUT = 1800; - private static final String SESSION_CONTEXT = "thread"; - private static final String COORDINATOR_CLASS = "jdbc"; - - private Session session; - - @Transactional - protected SessionFactory getSessionFactory(Database database) { - return getSessionFactory(database, false); - } - - @Transactional - protected SessionFactory getSessionFactory(Database database, Boolean privileged) { - final String url = "jdbc:" + database.getContainer().getImage().getJdbcMethod() + "://" + database.getContainer().getInternalName() + "/" + database.getInternalName(); - log.trace("hibernate jdbc url '{}', privileged: {}", url, privileged ? 'y' : 'n'); - final String username = database.getContainer().getImage().getEnvironment() + protected static Session getCurrentSession(ContainerImage image, Container container, Database database) { + final String url = "jdbc:" + image.getJdbcMethod() + "://" + container.getInternalName() + "/" + database.getInternalName(); + final String username = image.getEnvironment() .stream() - .filter(e -> e.getType().equals(privileged ? ContainerImageEnvironmentItemType.PRIVILEGED_USERNAME : ContainerImageEnvironmentItemType.USERNAME)) + .filter(e -> e.getType().equals(ContainerImageEnvironmentItemType.PRIVILEGED_USERNAME)) .map(ContainerImageEnvironmentItem::getValue) .collect(Collectors.toList()) .get(0); - final String password = database.getContainer().getImage().getEnvironment() + final String password = image.getEnvironment() .stream() - .filter(e -> e.getType().equals(privileged ? ContainerImageEnvironmentItemType.PRIVILEGED_PASSWORD : ContainerImageEnvironmentItemType.PASSWORD)) + .filter(e -> e.getType().equals(ContainerImageEnvironmentItemType.PRIVILEGED_PASSWORD)) .map(ContainerImageEnvironmentItem::getValue) .collect(Collectors.toList()) .get(0); - log.trace("container image {}", database.getContainer().getImage()); - final Configuration configuration = new Configuration() - .setProperty("hibernate.connection.url", url) - .setProperty("hibernate.connection.username", username) - .setProperty("hibernate.connection.password", password) - .setProperty("hibernate.connection.driver_class", database.getContainer().getImage().getDriverClass()) - .setProperty("hibernate.dialect", database.getContainer().getImage().getDialect()) - .setProperty("hibernate.current_session_context_class", SESSION_CONTEXT) - .setProperty("hibernate.transaction.coordinator_class", COORDINATOR_CLASS) - .setProperty("hibernate.hbm2ddl.auto", "update") - .setProperty("hibernate.jdbc.time_zone", "Europe/Vienna") - .setProperty("hibernate.c3p0.min_size", String.valueOf(MIN_SIZE)) - .setProperty("hibernate.c3p0.max_size", String.valueOf(MAX_SIZE)) - .setProperty("hibernate.c3p0.acquire_increment", String.valueOf(INCREMENT_SIZE)) - .setProperty("hibernate.c3p0.timeout", String.valueOf(TIMEOUT)) - .addAnnotatedClass(Query.class) - .addAnnotatedClass(Table.class) - .addAnnotatedClass(Column.class); - return configuration.buildSessionFactory(); - } - @Transactional - protected Session getSession(Database database, Boolean privileged) { - if (this.session == null) { - this.session = this.getSessionFactory(database, privileged) - .openSession(); + final Configuration config = new Configuration(); + config.configure("mariadb_hibernate.cfg.xml"); + config.setProperty("hibernate.connection.url", url); + config.setProperty("hibernate.connection.username", username); + config.setProperty("hibernate.connection.password", password); + config.setProperty("hibernate.connection.driver_class", image.getDriverClass()); + config.setProperty("hibernate.dialect", image.getDialect()); + final SessionFactory sessionFactory = config.buildSessionFactory(); + Session session = sessionFactory.getCurrentSession(); + if (!session.isOpen()) { + log.debug("Session is closed, opening..."); + session = sessionFactory.openSession(); } - if (!this.session.isOpen()) { - this.session = this.getSessionFactory(database, privileged) - .openSession(); - } else { - this.session = this.getSessionFactory(database, privileged) - .getCurrentSession(); + return session; + } + + protected static Long activeConnection(Session session) { + final NativeQuery<?> nativeQuery = session.createSQLQuery("SHOW STATUS LIKE 'threads_connected'"); + final List<?> result; + try { + result = nativeQuery.getResultList(); + } catch (PersistenceException e) { + log.error("Failed to collect number of used connections"); + /* ignore */ + return null; } - return this.session; + final Object[] row = (Object[]) result.get(0); + log.debug("current number of connections: {}", Long.parseLong(String.valueOf(row[1]))); + return Long.parseLong(String.valueOf(row[1])); } } diff --git a/fda-query-service/services/src/main/java/at/tuwien/service/impl/QueryServiceImpl.java b/fda-query-service/services/src/main/java/at/tuwien/service/impl/QueryServiceImpl.java index 451156032ced3d378506dec9710e9d3231f22d53..1241ef11ad08f85c53e5769145395c3ca4d6456a 100644 --- a/fda-query-service/services/src/main/java/at/tuwien/service/impl/QueryServiceImpl.java +++ b/fda-query-service/services/src/main/java/at/tuwien/service/impl/QueryServiceImpl.java @@ -23,7 +23,7 @@ import net.sf.jsqlparser.statement.select.*; import org.apache.commons.io.FileUtils; import org.apache.commons.lang.RandomStringUtils; import org.hibernate.Session; -import org.hibernate.SessionFactory; +import org.hibernate.Transaction; import org.hibernate.query.NativeQuery; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.core.io.InputStreamResource; @@ -79,28 +79,30 @@ public class QueryServiceImpl extends HibernateConnector implements QueryService throws QueryMalformedException, DatabaseNotFoundException, ImageNotSupportedException, ColumnParseException, TableMalformedException { /* find */ - final Database database = databaseService.find(databaseId); + final Database database = databaseService.find(containerId, databaseId); if (!database.getContainer().getImage().getRepository().equals("mariadb")) { throw new ImageNotSupportedException("Currently only MariaDB is supported"); } /* run query */ - final long startSession = System.currentTimeMillis(); - final Session session = getSession(database, true); - log.debug("opened hibernate session in {} ms", System.currentTimeMillis() - startSession); - session.beginTransaction(); + final Session session = getCurrentSession(database.getContainer().getImage(), database.getContainer(), database); + final Transaction transaction = session.beginTransaction(); /* prepare the statement */ final NativeQuery<?> nativeQuery = session.createSQLQuery( queryMapper.queryToRawTimestampedQuery(query.getQuery(), database, query.getExecution(), page, size)); - final int affectedTuples; + final List<?> result; try { - log.debug("execute raw view-only query {}", query); - affectedTuples = nativeQuery.executeUpdate(); - log.info("Execution on database id {} affected {} rows", databaseId, affectedTuples); - session.getTransaction() - .commit(); + log.debug("affected {} rows", nativeQuery.executeUpdate()); + result = nativeQuery.getResultList(); + activeConnection(session); + transaction.commit(); } catch (PersistenceException e) { log.error("Query not valid for this database"); + session.close(); throw new QueryMalformedException("Query not valid for this database", e); + } finally { + if (session.isOpen()) { + session.close(); + } } /* map the result to the tables (with respective columns) from the statement metadata */ final List<TableColumn> columns; @@ -110,10 +112,10 @@ public class QueryServiceImpl extends HibernateConnector implements QueryService log.error("Failed to map/parse columns."); throw new ColumnParseException("Failed to map/parse columns", e); } - final QueryResultDto result = queryMapper.resultListToQueryResultDto(columns, nativeQuery.getResultList()); - result.setId(query.getId()); - result.setResultNumber(countQueryResults(databaseId, query)); - return result; + final QueryResultDto dto = queryMapper.resultListToQueryResultDto(columns, result); + dto.setId(query.getId()); + dto.setResultNumber(countQueryResults(containerId, databaseId, query)); + return dto; } @Override @@ -123,28 +125,31 @@ public class QueryServiceImpl extends HibernateConnector implements QueryService ImageNotSupportedException, DatabaseConnectionException, TableMalformedException, PaginationException, ContainerNotFoundException { /* find */ - final Database database = databaseService.find(databaseId); + final Database database = databaseService.find(containerId, databaseId); final Table table = tableService.find(containerId, databaseId, tableId); /* run query */ - final long startSession = System.currentTimeMillis(); - final Session session = getSession(database, true); - log.debug("opened hibernate session in {} ms", System.currentTimeMillis() - startSession); - session.beginTransaction(); + final Session session = getCurrentSession(database.getContainer().getImage(), database.getContainer(), database); + final Transaction transaction = session.beginTransaction(); final NativeQuery<?> query = session.createSQLQuery( queryMapper.tableToRawFindAllQuery(table, timestamp, size, page)); - final int affectedTuples; + final List<?> resultList; try { - affectedTuples = query.executeUpdate(); - log.info("Found {} tuples in database id {}", affectedTuples, databaseId); + log.debug("affected {} tuples in database id {}", query.executeUpdate(), databaseId); + resultList = query.getResultList(); + activeConnection(session); + transaction.commit(); } catch (PersistenceException e) { log.error("Failed to find data"); + session.close(); throw new TableMalformedException("\"Failed to find data", e); + } finally { + if (session.isOpen()) { + session.close(); + } } - session.getTransaction() - .commit(); final QueryResultDto result; try { - result = queryMapper.queryTableToQueryResultDto(query.getResultList(), table); + result = queryMapper.queryTableToQueryResultDto(resultList, table); } catch (DateTimeException e) { log.error("Failed to parse date from the one stored in the metadata database"); throw new TableMalformedException("Could not parse date from format", e); @@ -158,24 +163,28 @@ public class QueryServiceImpl extends HibernateConnector implements QueryService throws TableNotFoundException, DatabaseNotFoundException, ImageNotSupportedException, DatabaseConnectionException, TableMalformedException, PaginationException, ContainerNotFoundException, FileStorageException { + final String filename = RandomStringUtils.randomAlphabetic(40) + ".csv"; /* find */ - final Database database = databaseService.find(databaseId); + final Database database = databaseService.find(containerId, databaseId); final Table table = tableService.find(containerId, databaseId, tableId); /* run query */ - final long startSession = System.currentTimeMillis(); - final Session session = getSession(database, true); - final String filename = RandomStringUtils.randomAlphabetic(40) + ".csv"; - log.debug("opened hibernate session in {} ms", System.currentTimeMillis() - startSession); - session.beginTransaction(); + final Session session = getCurrentSession(database.getContainer().getImage(), database.getContainer(), database); + final Transaction transaction = session.beginTransaction(); final NativeQuery<?> query = session.createSQLQuery( queryMapper.tableToRawExportQuery(table, timestamp, filename)); try { - query.executeUpdate(); + log.debug("affected tuples {}", query.executeUpdate()); + activeConnection(session); + transaction.commit(); } catch (PersistenceException e) { log.error("Failed to export table"); + session.close(); throw new TableMalformedException("Data not found", e); + } finally { + if (session.isOpen()) { + session.close(); + } } - session.getTransaction().commit(); /* read file */ final InputStream inputStream; try { @@ -193,24 +202,29 @@ public class QueryServiceImpl extends HibernateConnector implements QueryService @Transactional(readOnly = true) public ExportResource findOne(Long containerId, Long databaseId, Long queryId) throws DatabaseNotFoundException, ImageNotSupportedException, TableMalformedException, - ContainerNotFoundException, FileStorageException, QueryStoreException, QueryNotFoundException, QueryMalformedException { + ContainerNotFoundException, FileStorageException, QueryStoreException, QueryNotFoundException, + QueryMalformedException { + final String filename = RandomStringUtils.randomAlphabetic(40) + ".csv"; /* find */ - final Database database = databaseService.find(databaseId); + final Database database = databaseService.find(containerId, databaseId); final Query query = storeService.findOne(containerId, databaseId, queryId); /* run query */ - final long startSession = System.currentTimeMillis(); - final Session session = getSession(database, true); - final String filename = RandomStringUtils.randomAlphabetic(40) + ".csv"; - log.debug("opened hibernate session in {} ms", System.currentTimeMillis() - startSession); - session.beginTransaction(); + final Session session = getCurrentSession(database.getContainer().getImage(), database.getContainer(), database); + final Transaction transaction = session.beginTransaction(); final NativeQuery<?> query2 = session.createSQLQuery(queryMapper.queryToRawExportQuery(query, filename)); try { - query2.executeUpdate(); + log.debug("affected tuples {}", query2.executeUpdate()); + activeConnection(session); + transaction.commit(); } catch (PersistenceException e) { log.error("Failed to export query"); - throw new TableMalformedException("Data not found", e); + session.close(); + throw new TableMalformedException("Failed to export query", e); + } finally { + if (session.isOpen()) { + session.close(); + } } - session.getTransaction().commit(); /* read file */ final InputStream inputStream; try { @@ -232,26 +246,29 @@ public class QueryServiceImpl extends HibernateConnector implements QueryService throws DatabaseNotFoundException, TableNotFoundException, TableMalformedException, ImageNotSupportedException { /* find */ - final Database database = databaseService.find(databaseId); + final Database database = databaseService.find(containerId, databaseId); final Table table = tableService.find(containerId, databaseId, tableId); /* run query */ - final long startSession = System.currentTimeMillis(); - final Session session = getSession(database, false); - log.debug("opened hibernate session in {} ms", System.currentTimeMillis() - startSession); - session.beginTransaction(); + final Session session = getCurrentSession(database.getContainer().getImage(), database.getContainer(), database); + final Transaction transaction = session.beginTransaction(); final NativeQuery<BigInteger> query = session.createSQLQuery( queryMapper.tableToRawCountAllQuery(table, timestamp)); - final int affectedTuples; + final BigInteger count; try { - affectedTuples = query.executeUpdate(); - log.info("Counted {} tuples in table id {}", affectedTuples, tableId); + log.info("counted {} tuples in table id {}", query.executeUpdate(), tableId); + count = query.getSingleResult(); + activeConnection(session); + transaction.commit(); } catch (PersistenceException e) { log.error("Failed to count tuples"); - throw new TableMalformedException("Data not found", e); + session.close(); + throw new TableMalformedException("Failed to count tuples", e); + } finally { + if (session.isOpen()) { + session.close(); + } } - session.getTransaction() - .commit(); - return query.getSingleResult(); + return count; } @Override @@ -260,26 +277,28 @@ public class QueryServiceImpl extends HibernateConnector implements QueryService throws ImageNotSupportedException, TableMalformedException, DatabaseNotFoundException, TableNotFoundException, ContainerNotFoundException { /* find */ - final Database database = databaseService.find(databaseId); + final Database database = databaseService.find(containerId, databaseId); final Table table = tableService.find(containerId, databaseId, tableId); /* run query */ if (data.getData().size() == 0) return null; - final long startSession = System.currentTimeMillis(); - final Session session = getSession(database, true); - log.debug("opened hibernate session in {} ms", System.currentTimeMillis() - startSession); - session.beginTransaction(); + final Session session = getCurrentSession(database.getContainer().getImage(), database.getContainer(), database); + final Transaction transaction = session.beginTransaction(); + activeConnection(session); /* prepare the statement */ final InsertTableRawQuery raw; try { raw = queryMapper.tableCsvDtoToRawInsertQuery(table, data); } catch (DateTimeParseException e) { log.error("Failed to parse date: {}", e.getMessage()); + session.close(); return 0; } catch (NumberFormatException e) { log.error("Failed to parse number: {}", e.getMessage()); + session.close(); return 0; } catch (Exception e) { log.error("Failed for unknown reason: {}", e.getMessage()); + session.close(); return 0; } final NativeQuery<?> query = session.createSQLQuery(raw.getQuery()); @@ -293,14 +312,11 @@ public class QueryServiceImpl extends HibernateConnector implements QueryService throws ImageNotSupportedException, TableMalformedException, DatabaseNotFoundException, TableNotFoundException { /* find */ - final Database database = databaseService.find(databaseId); + final Database database = databaseService.find(containerId, databaseId); final Table table = tableService.find(containerId, databaseId, tableId); /* run query */ if (data.getData().size() == 0 || data.getKeys().size() == 0) return null; - final long startSession = System.currentTimeMillis(); - final Session session = getSession(database, true); - log.debug("opened hibernate session in {} ms", System.currentTimeMillis() - startSession); - session.beginTransaction(); + final Session session = getCurrentSession(database.getContainer().getImage(), database.getContainer(), database); /* prepare the statement */ final InsertTableRawQuery raw = queryMapper.tableCsvDtoToRawUpdateQuery(table, data); final NativeQuery<?> query = session.createSQLQuery(raw.getQuery()); @@ -317,14 +333,12 @@ public class QueryServiceImpl extends HibernateConnector implements QueryService throws ImageNotSupportedException, TableMalformedException, DatabaseNotFoundException, TableNotFoundException, TupleDeleteException { /* find */ - final Database database = databaseService.find(databaseId); + final Database database = databaseService.find(containerId, databaseId); final Table table = tableService.find(containerId, databaseId, tableId); /* run query */ if (data.getKeys().size() == 0) return; - final long startSession = System.currentTimeMillis(); - final Session session = getSession(database, true); - log.debug("opened hibernate session in {} ms", System.currentTimeMillis() - startSession); - session.beginTransaction(); + final Session session = getCurrentSession(database.getContainer().getImage(), database.getContainer(), database); + final Transaction transaction = session.beginTransaction(); /* prepare the statement */ final NativeQuery<?> query = session.createSQLQuery(queryMapper.tableCsvDtoToRawDeleteQuery(table, data)); final int[] idx = new int[]{0}; @@ -333,12 +347,18 @@ public class QueryServiceImpl extends HibernateConnector implements QueryService final int affectedTuples; try { affectedTuples = query.executeUpdate(); + activeConnection(session); + transaction.commit(); } catch (PersistenceException e) { - log.error("Could not insert data: {}", e.getMessage()); - throw new TableMalformedException("Could not insert data", e); + log.error("Failed to delete data"); + log.debug("failed to delete data: {}", e.getMessage()); + session.close(); + throw new TableMalformedException("Could not delete data", e); + } finally { + if (session.isOpen()) { + session.close(); + } } - session.getTransaction() - .commit(); if (affectedTuples == 0) { log.error("No tuples were deleted"); throw new TupleDeleteException("No tuples deleted"); @@ -353,48 +373,23 @@ public class QueryServiceImpl extends HibernateConnector implements QueryService throws ImageNotSupportedException, TableMalformedException, DatabaseNotFoundException, TableNotFoundException, ContainerNotFoundException { /* find */ - final Database database = databaseService.find(databaseId); + final Database database = databaseService.find(containerId, databaseId); final Table table = tableService.find(containerId, databaseId, tableId); - /* run query */ - final long startSession = System.currentTimeMillis(); - log.debug("opened hibernate session in {} ms", System.currentTimeMillis() - startSession); /* preparing the statements */ - final String rawTemp = queryMapper.generateTemporaryTableSQL(table); - final String rawDeleteTemp = queryMapper.dropTemporaryTableSQL(table); - final InsertTableRawQuery raw = queryMapper.pathToRawInsertQuery(table, data); - final String rawCopy = queryMapper.generateInsertFromTemporaryTableSQL(table); + final Session session1 = getCurrentSession(database.getContainer().getImage(), database.getContainer(), database); + session1.beginTransaction(); + final Session session2 = getCurrentSession(database.getContainer().getImage(), database.getContainer(), database); + session2.beginTransaction(); + final Session session3 = getCurrentSession(database.getContainer().getImage(), database.getContainer(), database); + session3.beginTransaction(); + final Session session4 = getCurrentSession(database.getContainer().getImage(), database.getContainer(), database); + session4.beginTransaction(); /* Create a temporary table, insert there, transfer with update on duplicate key and lastly drops the temporary table */ - execute(rawTemp, database); - execute(raw.getQuery(), database); - Integer i = execute(rawCopy, database); - execute(rawDeleteTemp, database); - return i; - } - - /** - * Executes an insert query on an active Hibernate session on a table with given id and returns the affected rows. - * - * @param rawQuery The query to execute - * @param database the database to execute the query in - * @return The affected rows, if successful. - * @throws TableMalformedException The table metadata is wrong. - */ - private Integer execute(String rawQuery, Database database) throws TableMalformedException { - final int affectedTuples; - Session session = getSession(database, true); - session.beginTransaction(); - NativeQuery<?> query = session.createSQLQuery(rawQuery); - try { - affectedTuples = query.executeUpdate(); - log.debug("Affected Tuples: {}", affectedTuples); - } catch (PersistenceException e) { - log.error("Could not insert data: {}", e.getMessage()); - log.throwing(e); - throw new TableMalformedException("Could not insert data", e); - } - session.getTransaction() - .commit(); + execute(session1.createSQLQuery(queryMapper.generateTemporaryTableSQL(table)), session1); + execute(session2.createSQLQuery(queryMapper.pathToRawInsertQuery(table, data).getQuery()), session2); + final Integer affectedTuples = execute(session3.createSQLQuery(queryMapper.generateInsertFromTemporaryTableSQL(table)), session3); + execute(session4.createSQLQuery(queryMapper.dropTemporaryTableSQL(table)), session4); return affectedTuples; } @@ -411,12 +406,17 @@ public class QueryServiceImpl extends HibernateConnector implements QueryService final int affectedTuples; try { affectedTuples = query.executeUpdate(); + session.getTransaction() + .commit(); } catch (PersistenceException e) { log.error("Could not insert data: {}", e.getMessage()); + session.close(); throw new TableMalformedException("Could not insert data", e); + } finally { + if (session.isOpen()) { + session.close(); + } } - session.getTransaction() - .commit(); return affectedTuples; } @@ -483,7 +483,7 @@ public class QueryServiceImpl extends HibernateConnector implements QueryService /* Checking if all columns exist */ for (SelectItem item : clauses) { if (item.toString().trim().equals("*")) { - log.warn("Do not use * in queries"); + log.error("Do not use * in queries"); continue; } final String clause = queryMapper.selectItemToEscapedString(item); @@ -508,36 +508,40 @@ public class QueryServiceImpl extends HibernateConnector implements QueryService /** * Counts the total number of tuples in the user database with given id for a given query object * - * @param databaseId The database id. - * @param query The query object. + * @param containerId The container id. + * @param databaseId The database id. + * @param query The query object. * @return The number of tuples this query returns. * @throws DatabaseNotFoundException The user database was not found in the container. * @throws TableMalformedException The table is malformed in the database (in the container). * @throws ImageNotSupportedException The database image is not supported. */ @Transactional(readOnly = true) - protected Long countQueryResults(Long databaseId, Query query) + protected Long countQueryResults(Long containerId, Long databaseId, Query query) throws DatabaseNotFoundException, TableMalformedException, ImageNotSupportedException { /* find */ - final Database database = databaseService.find(databaseId); + final Database database = databaseService.find(containerId, databaseId); /* run query */ - final long startSession = System.currentTimeMillis(); - final Session session = getSession(database, false); - log.debug("opened hibernate session in {} ms", System.currentTimeMillis() - startSession); - session.beginTransaction(); + final Session session = getCurrentSession(database.getContainer().getImage(), database.getContainer(), database); + final Transaction transaction = session.beginTransaction(); final NativeQuery<BigInteger> nativeQuery = session.createSQLQuery( queryMapper.queryToRawTimestampedCountQuery(query.getQuery(), database, query.getExecution())); - final int affectedTuples; + final BigInteger result; try { - affectedTuples = nativeQuery.executeUpdate(); - log.info("Counted {} tuples from query {}", affectedTuples, query.getId()); + log.debug("counted {} tuples from query {}", nativeQuery.executeUpdate(), query.getId()); + result = nativeQuery.getSingleResult(); + activeConnection(session); + transaction.commit(); } catch (PersistenceException e) { log.error("Failed to count tuples"); - throw new TableMalformedException("Data not found", e); + session.close(); + throw new TableMalformedException("Failed to count tuples", e); + } finally { + if (session.isOpen()) { + session.close(); + } } - session.getTransaction() - .commit(); - return nativeQuery.getSingleResult().longValue(); + return result.longValue(); } diff --git a/fda-query-service/services/src/main/java/at/tuwien/service/impl/RabbitMqServiceImpl.java b/fda-query-service/services/src/main/java/at/tuwien/service/impl/RabbitMqServiceImpl.java index eff83f34cf96c6b56d284b5e9d6fa02d3229a586..b7398a6694ccbb9b3b7a38ed423efaff96c080ea 100644 --- a/fda-query-service/services/src/main/java/at/tuwien/service/impl/RabbitMqServiceImpl.java +++ b/fda-query-service/services/src/main/java/at/tuwien/service/impl/RabbitMqServiceImpl.java @@ -1,48 +1,39 @@ package at.tuwien.service.impl; import at.tuwien.api.database.table.TableCsvDto; -import at.tuwien.config.AmqpConfig; -import at.tuwien.entities.database.table.Table; import at.tuwien.exception.*; import at.tuwien.service.MessageQueueService; import at.tuwien.service.QueryService; -import at.tuwien.service.TableService; import com.fasterxml.jackson.core.type.TypeReference; import com.fasterxml.jackson.databind.ObjectMapper; import com.rabbitmq.client.*; import lombok.extern.log4j.Log4j2; import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.scheduling.annotation.Scheduled; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import org.springframework.web.client.HttpClientErrorException; import java.io.IOException; import java.util.HashMap; -import java.util.concurrent.TimeoutException; @Log4j2 @Service public class RabbitMqServiceImpl implements MessageQueueService { - private Channel channel; - private final AmqpConfig amqpConfig; + private final Channel channel; private final ObjectMapper objectMapper; private final QueryService queryService; - private final TableService tableService; @Autowired - public RabbitMqServiceImpl(Channel channel, AmqpConfig amqpConfig, ObjectMapper objectMapper, - QueryService queryService, TableService tableService) { + public RabbitMqServiceImpl(Channel channel, ObjectMapper objectMapper, QueryService queryService) { this.channel = channel; - this.amqpConfig = amqpConfig; this.objectMapper = objectMapper; this.queryService = queryService; - this.tableService = tableService; } + @Override @Transactional(readOnly = true) - protected void createConsumer(String routingKey, Long containerId, Long databaseId, Long tableId) throws AmqpException { + public void createConsumer(String routingKey, Long containerId, Long databaseId, Long tableId) throws AmqpException { try { final String consumerTag = channel.basicConsume(routingKey, true, new Consumer() { @Override @@ -115,55 +106,9 @@ public class RabbitMqServiceImpl implements MessageQueueService { log.error("Failed to create consumer for table with id {}", tableId); throw new AmqpException("Failed to create consumer", e); } catch (Exception e) { - log.warn("Failed unknown: {}", e.getMessage()); + log.error("Failed unknown: {}", e.getMessage()); /* ignore */ } } - private Boolean hasConsumer(Table table) { - try { - final AMQP.Queue.DeclareOk response = channel.queueDeclarePassive(table.getTopic()); - log.trace("queue {} has {} messages waiting", table.getTopic(), response.getMessageCount()); - log.trace("queue {} has {} consumers", table.getTopic(), response.getConsumerCount()); - return response.getConsumerCount() > 0; - } catch (IOException e) { - log.error("Failed to check if queue {} has consumers", table.getTopic()); - return false; - } catch (AlreadyClosedException e) { - log.error("Failed to check, channel is already closed: {}", e.getMessage()); - return false; - } catch (Exception e) { - log.error("Failed to check: {}", e.getMessage()); - return false; - } - } - - @Override - @Transactional(readOnly = true) - @Scheduled(fixedRate = 5000) - public void renewConsumers() { - tableService.findAll() - .forEach(table -> { - final Boolean hasConsumer = hasConsumer(table); - if (!hasConsumer) { - try { - this.channel = amqpConfig.getChannel(); - } catch (IOException | TimeoutException e) { - log.error("Failed to renew channel"); - log.throwing(e); - /* ignore */ - } - } else { - log.trace("table {} has already one consumer, skipping.", table.getId()); - return; - } - try { - createConsumer(table.getTopic(), table.getDatabase().getContainer() - .getId(), table.getDatabase().getId(), table.getId()); - } catch (AmqpException e) { - /* ignore */ - } - }); - } - } diff --git a/fda-query-service/services/src/main/java/at/tuwien/service/impl/StoreServiceImpl.java b/fda-query-service/services/src/main/java/at/tuwien/service/impl/StoreServiceImpl.java index 270fb94be654155030746d307006fe263d2f4662..2badbf8feeda07894cfb0c299e925889ce26e46f 100644 --- a/fda-query-service/services/src/main/java/at/tuwien/service/impl/StoreServiceImpl.java +++ b/fda-query-service/services/src/main/java/at/tuwien/service/impl/StoreServiceImpl.java @@ -3,14 +3,12 @@ package at.tuwien.service.impl; import at.tuwien.api.database.query.ExecuteStatementDto; import at.tuwien.api.database.query.QueryResultDto; import at.tuwien.api.database.query.SaveStatementDto; -import at.tuwien.entities.container.Container; import at.tuwien.entities.user.User; -import at.tuwien.querystore.Query; import at.tuwien.entities.database.Database; import at.tuwien.exception.*; import at.tuwien.mapper.QueryMapper; import at.tuwien.mapper.StoreMapper; -import at.tuwien.service.ContainerService; +import at.tuwien.querystore.Query; import at.tuwien.service.DatabaseService; import at.tuwien.service.StoreService; import at.tuwien.service.UserService; @@ -18,10 +16,10 @@ import lombok.extern.log4j.Log4j2; import org.apache.commons.codec.digest.DigestUtils; import org.hibernate.*; import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.core.env.Environment; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; +import javax.persistence.PersistenceException; import java.security.Principal; import java.time.Instant; import java.util.List; @@ -34,77 +32,93 @@ public class StoreServiceImpl extends HibernateConnector implements StoreService private final StoreMapper storeMapper; private final UserService userService; private final DatabaseService databaseService; - private final ContainerService containerService; @Autowired public StoreServiceImpl(QueryMapper queryMapper, StoreMapper storeMapper, UserService userService, - DatabaseService databaseService, ContainerService containerService) { + DatabaseService databaseService) { this.queryMapper = queryMapper; this.storeMapper = storeMapper; this.userService = userService; this.databaseService = databaseService; - this.containerService = containerService; } @Override @Transactional(readOnly = true) - public List<Query> findAll(Long containerId, Long databaseId) throws DatabaseNotFoundException, + public List<at.tuwien.querystore.Query> findAll(Long containerId, Long databaseId) throws DatabaseNotFoundException, ImageNotSupportedException, QueryStoreException, ContainerNotFoundException { /* find */ - final Container container = containerService.find(containerId); - final Database database = databaseService.find(databaseId); + final Database database = databaseService.find(containerId, databaseId); if (!database.getContainer().getImage().getRepository().equals("mariadb")) { throw new ImageNotSupportedException("Currently only MariaDB is supported"); } - log.debug("find all queries in database id {}", databaseId); + log.trace("find all queries in database id {}", databaseId); /* run query */ - final Session session = getSession(database, true); + final Session session = getCurrentSession(database.getContainer().getImage(), database.getContainer(), database); final Transaction transaction = session.beginTransaction(); + activeConnection(session); /* use jpq to select all */ - final org.hibernate.query.Query<Query> queries = session.createQuery("select q from Query q", Query.class); - transaction.commit(); - final List<Query> out = queries.list(); - log.info("Found {} queries", out.size()); - log.debug("found queries {}", out); - return out; + final org.hibernate.query.Query<at.tuwien.querystore.Query> queries = session.createQuery("select q from Query q", + at.tuwien.querystore.Query.class); + final List<Query> out; + try { + out = queries.list(); + transaction.commit(); + return out; + } catch (PersistenceException e) { + log.error("Failed to find all queries"); + session.close(); + throw new QueryStoreException("Failed to find all queries"); + } finally { + if (session.isOpen()) { + session.close(); + } + } } @Override @Transactional(readOnly = true) - public Query findOne(Long containerId, Long databaseId, Long queryId) throws DatabaseNotFoundException, - ImageNotSupportedException, QueryNotFoundException, ContainerNotFoundException { + public at.tuwien.querystore.Query findOne(Long containerId, Long databaseId, Long queryId) throws DatabaseNotFoundException, + ImageNotSupportedException, QueryNotFoundException, QueryStoreException { /* find */ - final Container container = containerService.find(containerId); - final Database database = databaseService.find(databaseId); + final Database database = databaseService.find(containerId, databaseId); if (!database.getContainer().getImage().getRepository().equals("mariadb")) { throw new ImageNotSupportedException("Currently only MariaDB is supported"); } - log.debug("find one query in database id {} with id {}", databaseId, queryId); /* run query */ - final Session session = getSession(database, true); + final Session session = getCurrentSession(database.getContainer().getImage(), database.getContainer(), database); final Transaction transaction = session.beginTransaction(); /* use jpa to select one */ - final org.hibernate.query.Query<Query> query = session.createQuery( + final org.hibernate.query.Query<at.tuwien.querystore.Query> query = session.createQuery( "from Query where cid = :cid and dbid = :dbid and id = :id", - Query.class); + at.tuwien.querystore.Query.class); query.setParameter("cid", containerId); query.setParameter("dbid", databaseId); query.setParameter("id", queryId); - final Query result = query.uniqueResult(); - transaction.commit(); + final at.tuwien.querystore.Query result; + try { + result = query.uniqueResult(); + activeConnection(session); + transaction.commit(); + } catch (PersistenceException e) { + log.error("Failed to find single query"); + session.close(); + throw new QueryStoreException("Failed to find single query", e); + } finally { + if (session.isOpen()) { + session.close(); + } + } if (result == null) { log.error("Query not found with id {}", queryId); throw new QueryNotFoundException("Query not found"); } - log.info("Found query with id {}", queryId); - log.debug("Found query {}", result); return result; } @Override @Transactional(readOnly = true) - public Query insert(Long containerId, Long databaseId, QueryResultDto result, SaveStatementDto metadata, - Principal principal) + public at.tuwien.querystore.Query insert(Long containerId, Long databaseId, QueryResultDto result, SaveStatementDto metadata, + Principal principal) throws QueryStoreException, DatabaseNotFoundException, ImageNotSupportedException, ContainerNotFoundException, UserNotFoundException { return insert(containerId, databaseId, result, queryMapper.saveStatementDtoToExecuteStatementDto(metadata), @@ -113,13 +127,12 @@ public class StoreServiceImpl extends HibernateConnector implements StoreService @Override @Transactional(readOnly = true) - public Query insert(Long containerId, Long databaseId, QueryResultDto result, ExecuteStatementDto metadata, - Principal principal, Instant execution) + public at.tuwien.querystore.Query insert(Long containerId, Long databaseId, QueryResultDto result, ExecuteStatementDto metadata, + Principal principal, Instant execution) throws QueryStoreException, DatabaseNotFoundException, ImageNotSupportedException, ContainerNotFoundException, UserNotFoundException { /* find */ - final Container container = containerService.find(containerId); - final Database database = databaseService.find(databaseId); + final Database database = databaseService.find(containerId, databaseId); if (!database.getContainer().getImage().getRepository().equals("mariadb")) { throw new ImageNotSupportedException("Currently only MariaDB is supported"); } @@ -127,9 +140,9 @@ public class StoreServiceImpl extends HibernateConnector implements StoreService /* user */ final User creator = userService.findByUsername(principal.getName()); /* save */ - final Session session = getSession(database, true); + final Session session = getCurrentSession(database.getContainer().getImage(), database.getContainer(), database); final Transaction transaction = session.beginTransaction(); - final Query query = Query.builder() + final at.tuwien.querystore.Query query = at.tuwien.querystore.Query.builder() .cid(containerId) .dbid(databaseId) .query(metadata.getStatement()) @@ -140,39 +153,60 @@ public class StoreServiceImpl extends HibernateConnector implements StoreService .execution(execution) .createdBy(creator.getId()) .build(); - session.save(query); - transaction.commit(); - /* store the result in the query store */ - log.info("Saved query with id {}", query.getId()); - log.debug("saved query {}", query); - return query; + try { + session.save(query); + activeConnection(session); + transaction.commit(); + /* store the result in the query store */ + log.info("Saved query with id {}", query.getId()); + log.debug("saved query {}", query); + return query; + } catch (PersistenceException e) { + log.error("Failed to save query"); + session.close(); + throw new QueryStoreException("Failed to save query", e); + } finally { + if (session.isOpen()) { + session.close(); + } + } } @Override @Transactional(readOnly = true) - public Query update(Long containerId, Long databaseId, QueryResultDto result, Long resultNumber, Query query) - throws QueryStoreException, DatabaseNotFoundException, ImageNotSupportedException, - ContainerNotFoundException { + public at.tuwien.querystore.Query update(Long containerId, Long databaseId, QueryResultDto result, Long resultNumber, + at.tuwien.querystore.Query query) + throws DatabaseNotFoundException, ImageNotSupportedException, QueryStoreException { /* find */ - final Container container = containerService.find(containerId); - final Database database = databaseService.find(databaseId); + final Database database = databaseService.find(containerId, databaseId); if (!database.getContainer().getImage().getRepository().equals("mariadb")) { throw new ImageNotSupportedException("Currently only MariaDB is supported"); } log.debug("Update database id {}, metadata {}", databaseId, query); /* save */ - final Session session = getSession(database, true); + final Session session = getCurrentSession(database.getContainer().getImage(), database.getContainer(), database); final Transaction transaction = session.beginTransaction(); query.setQueryHash(DigestUtils.sha256Hex(query.getQuery())); query.setResultNumber(resultNumber); query.setResultHash(storeMapper.queryResultDtoToString(result)); - session.update(query); - transaction.commit(); - /* store the result in the query store */ - log.info("Update query with id {}", query.getId()); - log.debug("saved query {}", query); - return query; + try { + session.update(query); + activeConnection(session); + transaction.commit(); + /* store the result in the query store */ + log.info("Update query with id {}", query.getId()); + log.debug("saved query {}", query); + return query; + } catch (PersistenceException e) { + log.error("Failed to update query"); + session.close(); + throw new QueryStoreException("Failed to update query", e); + } finally { + if (session.isOpen()) { + session.close(); + } + } } diff --git a/fda-query-service/services/src/main/java/at/tuwien/service/impl/TableServiceImpl.java b/fda-query-service/services/src/main/java/at/tuwien/service/impl/TableServiceImpl.java index 61e51d44ca682a268c50f2ebd75eaa680c4919ee..9f7d7160f254a7556bc211c6067d0a41f71e301d 100644 --- a/fda-query-service/services/src/main/java/at/tuwien/service/impl/TableServiceImpl.java +++ b/fda-query-service/services/src/main/java/at/tuwien/service/impl/TableServiceImpl.java @@ -7,12 +7,11 @@ import at.tuwien.exception.DatabaseNotFoundException; import at.tuwien.exception.QueryMalformedException; import at.tuwien.exception.TableNotFoundException; import at.tuwien.mapper.QueryMapper; -import at.tuwien.repository.jpa.DatabaseRepository; import at.tuwien.repository.jpa.TableRepository; +import at.tuwien.service.DatabaseService; import at.tuwien.service.TableService; import lombok.extern.log4j.Log4j2; import org.hibernate.Session; -import org.hibernate.SessionFactory; import org.hibernate.Transaction; import org.hibernate.query.NativeQuery; import org.springframework.beans.factory.annotation.Autowired; @@ -28,13 +27,13 @@ public class TableServiceImpl extends HibernateConnector implements TableService private final QueryMapper queryMapper; private final TableRepository tableRepository; - private final DatabaseRepository databaseRepository; + private final DatabaseService databaseService; @Autowired - public TableServiceImpl(QueryMapper queryMapper, TableRepository tableRepository, DatabaseRepository databaseRepository) { + public TableServiceImpl(QueryMapper queryMapper, TableRepository tableRepository, DatabaseService databaseService) { this.queryMapper = queryMapper; this.tableRepository = tableRepository; - this.databaseRepository = databaseRepository; + this.databaseService = databaseService; } @Override @@ -61,28 +60,28 @@ public class TableServiceImpl extends HibernateConnector implements TableService public List<TableHistoryDto> findHistory(Long containerId, Long databaseId, Long tableId) throws DatabaseNotFoundException, QueryMalformedException, TableNotFoundException { /* find */ - final Optional<Database> database = databaseRepository.findById(databaseId); - if (database.isEmpty()) { - log.error("Database with id {} not found in metadata database", databaseId); - throw new DatabaseNotFoundException("Database not found in metadata database"); - } + final Database database = databaseService.find(containerId, databaseId); final Table table = find(containerId, databaseId, tableId); /* run query */ - final Session session = getSession(database.get(), true); + final Session session = getCurrentSession(database.getContainer().getImage(), database.getContainer(), database); final Transaction transaction = session.beginTransaction(); /* use jpa to select one */ final NativeQuery<?> query = session.createSQLQuery(queryMapper.historyRawQuery(table)); + final List<?> result; try { log.debug("affected tuples {}", query.executeUpdate()); + result = query.getResultList(); + transaction.commit(); } catch (PersistenceException e) { log.error("Failed to obtain query history"); + session.close(); throw new QueryMalformedException("Failed to obtain query history", e); + } finally { + if (session.isOpen()) { + session.close(); + } } - final List<TableHistoryDto> history = queryMapper.resultListToTableHistoryDto(table, query.getResultList()); - transaction.commit(); - log.info("Found table history with {} tuples", history.size()); - log.debug("Found table history {}", history); - return history; + return queryMapper.resultListToTableHistoryDto(table, result); } } diff --git a/fda-table-service/rest-service/src/main/java/at/tuwien/endpoints/AbstractEndpoint.java b/fda-table-service/rest-service/src/main/java/at/tuwien/endpoints/AbstractEndpoint.java new file mode 100644 index 0000000000000000000000000000000000000000..033143372b67d5dd7a6aec5ed7aa2d48aa017a66 --- /dev/null +++ b/fda-table-service/rest-service/src/main/java/at/tuwien/endpoints/AbstractEndpoint.java @@ -0,0 +1,93 @@ +package at.tuwien.endpoints; + +import at.tuwien.entities.database.Database; +import at.tuwien.exception.DatabaseNotFoundException; +import at.tuwien.service.DatabaseService; +import at.tuwien.service.TableService; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.security.core.Authentication; + +import java.security.Principal; +import java.util.List; + + +@Slf4j +public abstract class AbstractEndpoint { + + private final TableService tableService; + private final DatabaseService databaseService; + + @Autowired + protected AbstractEndpoint(TableService tableService, DatabaseService databaseService) { + this.tableService = tableService; + this.databaseService = databaseService; + } + + protected Boolean hasDatabasePermission(Long containerId, Long databaseId, String permissionCode, + Principal principal) { + final Database database; + try { + database = databaseService.find(containerId, databaseId); + } catch (DatabaseNotFoundException e) { + log.error("Failed to find database with id {}", databaseId); + return false; + } + if (principal != null && database.getCreator().getUsername().equals(principal.getName())) { + log.debug("grant permission {} because user is creator of database with id {}", permissionCode, databaseId); + return true; + } + /* view-only operations are allowed on public databases */ + if (database.getIsPublic() && List.of("TABLE_CREATE", "TABLES_VIEW").contains(permissionCode)) { + log.debug("grant permission {} because database is public", permissionCode); + return true; + } + /* modification operations are limited to the creator */ + if (principal == null) { + log.debug("failed to grant permission {} because principal is null", permissionCode); + return false; + } + final Authentication authentication = (Authentication) principal /* with pre-authorization this always holds */; + if (authentication.getAuthorities().stream().noneMatch(a -> a.getAuthority().equals("ROLE_RESEARCHER"))) { + log.debug("failed to grant permission {} because current user misses authority 'ROLE_RESEARCHER'", + permissionCode); + return false; + } + log.debug("grant permission {} because user is creator", permissionCode); + return true; + } + + protected Boolean hasTablePermission(Long containerId, Long databaseId, Long tableId, String permissionCode, + Principal principal) { + final Database database; + try { + database = databaseService.find(containerId, databaseId); + } catch (DatabaseNotFoundException e) { + log.debug("Failed to find database with id {}", databaseId); + return false; + } + if (principal != null && database.getCreator().getUsername().equals(principal.getName())) { + log.debug("grant permission {} because user is creator of database with id {}", permissionCode, databaseId); + return true; + } + /* view-only operations are allowed on public databases */ + if (database.getIsPublic() && List.of("TABLE_INFO").contains(permissionCode)) { + log.debug("grant permission {} because database is public", permissionCode); + return true; + } + /* modification operations are limited to the creator */ + if (principal == null) { + log.debug("failed to grant permission {} because principal is null", permissionCode); + return false; + } + final Authentication authentication = (Authentication) principal /* with pre-authorization this always holds */; + if (authentication.getAuthorities().stream().noneMatch(a -> a.getAuthority().equals("ROLE_RESEARCHER"))) { + log.debug("failed to grant permission {} because current user misses authority 'ROLE_RESEARCHER'", + permissionCode); + return false; + } + log.debug("grant permission {} because user is creator", permissionCode); + return true; + } + +} diff --git a/fda-table-service/rest-service/src/main/java/at/tuwien/endpoints/TableEndpoint.java b/fda-table-service/rest-service/src/main/java/at/tuwien/endpoints/TableEndpoint.java index baff04fead52add2f48f34edb57043a7c29725f4..6f0c1c1aeaa36f2e0af8f6f15434c19159d36b00 100644 --- a/fda-table-service/rest-service/src/main/java/at/tuwien/endpoints/TableEndpoint.java +++ b/fda-table-service/rest-service/src/main/java/at/tuwien/endpoints/TableEndpoint.java @@ -4,6 +4,7 @@ import at.tuwien.api.database.table.*; import at.tuwien.entities.database.table.Table; import at.tuwien.exception.*; import at.tuwien.mapper.TableMapper; +import at.tuwien.service.DatabaseService; import at.tuwien.service.MessageQueueService; import at.tuwien.service.TableService; import io.swagger.v3.oas.annotations.Operation; @@ -18,6 +19,7 @@ import org.springframework.web.bind.annotation.*; import javax.validation.Valid; import javax.validation.constraints.NotNull; +import javax.ws.rs.NotAllowedException; import java.security.Principal; import java.util.List; import java.util.stream.Collectors; @@ -26,14 +28,16 @@ import java.util.stream.Collectors; @CrossOrigin(origins = "*") @RestController @RequestMapping("/api/container/{id}/database/{databaseId}/table") -public class TableEndpoint { +public class TableEndpoint extends AbstractEndpoint { private final TableService tableService; private final MessageQueueService amqpService; private final TableMapper tableMapper; @Autowired - public TableEndpoint(TableService tableService, MessageQueueService amqpService, TableMapper tableMapper) { + public TableEndpoint(TableService tableService, DatabaseService databaseService, MessageQueueService amqpService, + TableMapper tableMapper) { + super(tableService, databaseService); this.tableService = tableService; this.amqpService = amqpService; this.tableMapper = tableMapper; @@ -41,12 +45,15 @@ public class TableEndpoint { @GetMapping @Transactional(readOnly = true) - @PreAuthorize("hasRole('ROLE_RESEARCHER') and hasPermission(#databaseId, 'TABLE_VIEW')") @Operation(summary = "List all tables", security = @SecurityRequirement(name = "bearerAuth")) public ResponseEntity<List<TableBriefDto>> findAll(@NotNull @PathVariable("id") Long containerId, @NotNull @PathVariable("databaseId") Long databaseId, Principal principal) throws DatabaseNotFoundException { + if (!hasDatabasePermission(containerId, databaseId, "TABLES_VIEW", principal)) { + log.error("Missing table view permission"); + throw new NotAllowedException("Missing table view permission"); + } return ResponseEntity.ok(tableService.findAll(containerId, databaseId, principal) .stream() .map(tableMapper::tableToTableBriefDto) @@ -55,7 +62,6 @@ public class TableEndpoint { @PostMapping @Transactional - @PreAuthorize("hasRole('ROLE_RESEARCHER') and hasPermission(#databaseId, 'TABLE_CREATE')") @Operation(summary = "Create a table", security = @SecurityRequirement(name = "bearerAuth")) public ResponseEntity<TableBriefDto> create(@NotNull @PathVariable("id") Long containerId, @NotNull @PathVariable("databaseId") Long databaseId, @@ -63,6 +69,10 @@ public class TableEndpoint { Principal principal) throws ImageNotSupportedException, DatabaseNotFoundException, TableMalformedException, AmqpException, TableNameExistsException, ContainerNotFoundException, UserNotFoundException { + if (!hasDatabasePermission(containerId, databaseId, "TABLE_CREATE", principal)) { + log.error("Missing table create permission"); + throw new NotAllowedException("Missing table create permission"); + } final Table table = tableService.createTable(containerId, databaseId, createDto, principal); amqpService.create(table); return ResponseEntity.status(HttpStatus.CREATED) @@ -72,13 +82,16 @@ public class TableEndpoint { @GetMapping("/{tableId}") @Transactional(readOnly = true) - @PreAuthorize("hasRole('ROLE_RESEARCHER') and hasPermission(#databaseId, 'TABLE_VIEW')") @Operation(summary = "Get information about table", security = @SecurityRequirement(name = "bearerAuth")) public ResponseEntity<TableDto> findById(@NotNull @PathVariable("id") Long containerId, @NotNull @PathVariable("databaseId") Long databaseId, @NotNull @PathVariable("tableId") Long tableId, Principal principal) throws TableNotFoundException, DatabaseNotFoundException, ContainerNotFoundException { + if (!hasTablePermission(containerId, databaseId, tableId, "TABLE_INFO", principal)) { + log.error("Missing table view permission"); + throw new NotAllowedException("Missing table view permission"); + } final Table table = tableService.findById(containerId, databaseId, tableId, principal); log.debug(table); TableDto tableDto = tableMapper.tableToTableDto(table); @@ -88,19 +101,20 @@ public class TableEndpoint { @PutMapping("/{tableId}") @Transactional - @PreAuthorize("hasRole('ROLE_RESEARCHER') and hasPermission(#databaseId, 'TABLE_UPDATE')") @Operation(summary = "Update a table", security = @SecurityRequirement(name = "bearerAuth")) - public ResponseEntity<TableBriefDto> update(@NotNull @PathVariable("id") Long id, + public ResponseEntity<TableBriefDto> update(@NotNull @PathVariable("id") Long containerId, @NotNull @PathVariable("databaseId") Long databaseId, @NotNull @PathVariable("tableId") Long tableId, Principal principal) { - // TODO + if (!hasTablePermission(containerId, databaseId, tableId, "TABLE_UPDATE", principal)) { + log.error("Missing table update permission"); + throw new NotAllowedException("Missing table update permission"); + } return ResponseEntity.unprocessableEntity().body(new TableBriefDto()); } @DeleteMapping("/{tableId}") @Transactional - @PreAuthorize("hasRole('ROLE_DEVELOPER') or hasRole('ROLE_DATA_STEWARD')") @Operation(summary = "Delete a table", security = @SecurityRequirement(name = "bearerAuth")) @ResponseStatus(HttpStatus.OK) public void delete(@NotNull @PathVariable("id") Long containerId, @@ -108,7 +122,11 @@ public class TableEndpoint { @NotNull @PathVariable("tableId") Long tableId, Principal principal) throws TableNotFoundException, DatabaseNotFoundException, ImageNotSupportedException, - DataProcessingException, ContainerNotFoundException { + DataProcessingException, ContainerNotFoundException, TableMalformedException { + if (!hasTablePermission(containerId, databaseId, tableId, "TABLE_DELETE", principal)) { + log.error("Missing table delete permission"); + throw new NotAllowedException("Missing table delete permission"); + } tableService.deleteTable(containerId, databaseId, tableId, principal); } diff --git a/fda-table-service/services/src/main/java/at/tuwien/auth/UserPermissionEvaluator.java b/fda-table-service/services/src/main/java/at/tuwien/auth/UserPermissionEvaluator.java deleted file mode 100644 index f91076063f5b25b33b2bdc26a4dc6a6544d5c34f..0000000000000000000000000000000000000000 --- a/fda-table-service/services/src/main/java/at/tuwien/auth/UserPermissionEvaluator.java +++ /dev/null @@ -1,71 +0,0 @@ -package at.tuwien.auth; - -import at.tuwien.api.user.UserDetailsDto; -import at.tuwien.entities.database.Database; -import at.tuwien.exception.DatabaseNotFoundException; -import at.tuwien.mapper.DatabaseMapper; -import at.tuwien.service.DatabaseService; -import lombok.extern.slf4j.Slf4j; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.security.access.PermissionEvaluator; -import org.springframework.security.core.Authentication; -import org.springframework.stereotype.Component; - -import java.io.Serializable; - -@Slf4j -@Component -public class UserPermissionEvaluator implements PermissionEvaluator { - - private final DatabaseMapper databaseMapper; - private final DatabaseService databaseService; - - @Autowired - public UserPermissionEvaluator(DatabaseMapper databaseMapper, DatabaseService databaseService) { - this.databaseMapper = databaseMapper; - this.databaseService = databaseService; - } - - @Override - public boolean hasPermission(Authentication auth, Object targetDomainObject, Object permission) { - if (auth == null) { - log.error("Authentication principal is null"); - return false; - } - if (!(targetDomainObject instanceof Long)) { - log.error("Domain is not of type Long"); - return false; - } - if (!(permission instanceof String)) { - log.error("Permission is not of type String"); - return false; - } - final UserDetailsDto detailsDto = (UserDetailsDto) auth.getPrincipal(); - final Long targetDomainId = (Long) targetDomainObject; - final String permissionCode = (String) permission; - final Database database; - try { - database = databaseService.findPublicOrMineById(targetDomainId, - databaseMapper.userDetailsDtoToPrincipal(detailsDto)); - } catch (DatabaseNotFoundException e) { - log.error("Failed to find database with id {}", targetDomainId); - return false; - } - switch (permissionCode) { - case "TABLE_VIEW": - case "TABLE_CREATE": - case "TABLE_UPDATE": - if (database.getIsPublic()) { - return true; - } - /* only the creator can view */ - return database.getCreator().getId().equals(detailsDto.getId()); - } - return false; - } - - @Override - public boolean hasPermission(Authentication auth, Serializable targetId, String targetType, Object permission) { - return false; - } -} diff --git a/fda-table-service/services/src/main/java/at/tuwien/repository/jpa/DatabaseRepository.java b/fda-table-service/services/src/main/java/at/tuwien/repository/jpa/DatabaseRepository.java index 48101a1099d5834d01b37322c90e765d8fe1f3f2..f945f6eb2b36842ab96cfbab21e461617c9e76c4 100644 --- a/fda-table-service/services/src/main/java/at/tuwien/repository/jpa/DatabaseRepository.java +++ b/fda-table-service/services/src/main/java/at/tuwien/repository/jpa/DatabaseRepository.java @@ -11,11 +11,13 @@ import java.util.Optional; @Repository public interface DatabaseRepository extends JpaRepository<Database, Long> { - @Query("select d from Database d where d.id = :databaseId and (d.isPublic = true or d.creator.username = " + - ":username)") - Optional<Database> findPublicOrMine(@Param("databaseId") Long databaseId, @Param("username") String username); + @Query("select d from Database d where d.container.id = :containerId and d.id = :databaseId and (d.isPublic = true or d.creator.username = :username)") + Optional<Database> findPublicOrMine(@Param("containerId") Long containerId, @Param("databaseId") Long databaseId, @Param("username") String username); - @Query("select d from Database d where d.isPublic = true and d.id = :databaseId") - Optional<Database> findPublic(@Param("databaseId") Long databaseId); + @Query("select d from Database d where d.container.id = :containerId and d.id = :databaseId and d.isPublic = true") + Optional<Database> findPublic(@Param("containerId") Long containerId, @Param("databaseId") Long databaseId); + + @Query("select d from Database d where d.container.id = :containerId and d.id = :databaseId") + Optional<Database> findByContainerIdAndDatabaseId(@Param("containerId") Long containerId, @Param("databaseId") Long databaseId); } diff --git a/fda-table-service/services/src/main/java/at/tuwien/service/DatabaseService.java b/fda-table-service/services/src/main/java/at/tuwien/service/DatabaseService.java index cb5a218a3dca683d55ed6032f3b26964ff4215a9..b043ae456d45d143d78fa8c7209f8c47b8e8cb47 100644 --- a/fda-table-service/services/src/main/java/at/tuwien/service/DatabaseService.java +++ b/fda-table-service/services/src/main/java/at/tuwien/service/DatabaseService.java @@ -10,10 +10,13 @@ public interface DatabaseService { /** * Finds a specific database for a given id in the metadata database. * - * @param databaseId The database id. - * @param principal The principal. + * @param containerId The container id. + * @param databaseId The database id. + * @param principal The principal. * @return The database if found. * @throws DatabaseNotFoundException The database was not found. */ - Database findPublicOrMineById(Long databaseId, Principal principal) throws DatabaseNotFoundException; + Database findPublicOrMineById(Long containerId, Long databaseId, Principal principal) throws DatabaseNotFoundException; + + Database find(Long container, Long databaseId) throws DatabaseNotFoundException; } diff --git a/fda-table-service/services/src/main/java/at/tuwien/service/TableService.java b/fda-table-service/services/src/main/java/at/tuwien/service/TableService.java index 965b413a5231a14d22a0800ee4b81c53615bc1dc..b541cd140e4e7bb65df6ccac0ba57e535e5b035c 100644 --- a/fda-table-service/services/src/main/java/at/tuwien/service/TableService.java +++ b/fda-table-service/services/src/main/java/at/tuwien/service/TableService.java @@ -33,7 +33,7 @@ public interface TableService { */ void deleteTable(Long containerId, Long databaseId, Long tableId, Principal principal) throws TableNotFoundException, DatabaseNotFoundException, - ImageNotSupportedException, DataProcessingException, ContainerNotFoundException; + ImageNotSupportedException, DataProcessingException, ContainerNotFoundException, TableMalformedException; /** * Find a table by database-table id pair diff --git a/fda-table-service/services/src/main/java/at/tuwien/service/impl/DatabaseServiceImpl.java b/fda-table-service/services/src/main/java/at/tuwien/service/impl/DatabaseServiceImpl.java index 21a1ce56c4b01a70a20f5e573d99f3b6c67805aa..0b6ee9c735df1bb87233b1651749126d64e12e51 100644 --- a/fda-table-service/services/src/main/java/at/tuwien/service/impl/DatabaseServiceImpl.java +++ b/fda-table-service/services/src/main/java/at/tuwien/service/impl/DatabaseServiceImpl.java @@ -23,15 +23,25 @@ public class DatabaseServiceImpl implements DatabaseService { } @Override - public Database findPublicOrMineById(Long databaseId, Principal principal) throws DatabaseNotFoundException { + public Database findPublicOrMineById(Long containerId, Long databaseId, Principal principal) throws DatabaseNotFoundException { final Optional<Database> database; if (principal == null) { - database = databaseRepository.findPublic(databaseId); + database = databaseRepository.findPublic(containerId, databaseId); } else { - database = databaseRepository.findPublicOrMine(databaseId, principal.getName()); + database = databaseRepository.findPublicOrMine(containerId, databaseId, principal.getName()); } if (database.isEmpty()) { - log.warn("could not find database with id {}", databaseId); + log.error("Failed to find database with id {}", databaseId); + throw new DatabaseNotFoundException("could not find database with this id"); + } + return database.get(); + } + + @Override + public Database find(Long container, Long databaseId) throws DatabaseNotFoundException { + final Optional<Database> database = databaseRepository.findByContainerIdAndDatabaseId(container, databaseId); + if (database.isEmpty()) { + log.error("Failed to find database with id {}", databaseId); throw new DatabaseNotFoundException("could not find database with this id"); } return database.get(); diff --git a/fda-table-service/services/src/main/java/at/tuwien/service/impl/HibernateConnector.java b/fda-table-service/services/src/main/java/at/tuwien/service/impl/HibernateConnector.java index 050e0b858741f202bcd7d202cd0ee76102ebe4d2..28b7cfd61ed555c9944f657c91f15565328c1f55 100644 --- a/fda-table-service/services/src/main/java/at/tuwien/service/impl/HibernateConnector.java +++ b/fda-table-service/services/src/main/java/at/tuwien/service/impl/HibernateConnector.java @@ -13,11 +13,13 @@ import org.hibernate.boot.Metadata; import org.hibernate.boot.MetadataSources; import org.hibernate.boot.registry.StandardServiceRegistryBuilder; import org.hibernate.cfg.Configuration; +import org.hibernate.query.NativeQuery; import org.hibernate.service.ServiceRegistry; import org.springframework.core.io.ClassPathResource; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; +import javax.persistence.PersistenceException; import java.io.IOException; import java.io.InputStream; import java.util.HashMap; @@ -60,6 +62,21 @@ public abstract class HibernateConnector { return session; } + protected static Long activeConnection(Session session) { + final NativeQuery<?> nativeQuery = session.createSQLQuery("SHOW STATUS LIKE 'threads_connected'"); + final List<?> result; + try { + result = nativeQuery.getResultList(); + } catch (PersistenceException e) { + log.error("Failed to collect number of used connections"); + /* ignore */ + return null; + } + final Object[] row = (Object[]) result.get(0); + log.debug("current number of connections: {}", Long.parseLong(String.valueOf(row[1]))); + return Long.parseLong(String.valueOf(row[1])); + } + /** * Checks if the word is in the reserved word csv (i.e. an SQL keyword), solves issue 106 * diff --git a/fda-table-service/services/src/main/java/at/tuwien/service/impl/TableServiceImpl.java b/fda-table-service/services/src/main/java/at/tuwien/service/impl/TableServiceImpl.java index 1fa8667d63b7a360c212aaece8598d1a3c13d74b..120ff8b49100cdac514969acbc58c4946e55971e 100644 --- a/fda-table-service/services/src/main/java/at/tuwien/service/impl/TableServiceImpl.java +++ b/fda-table-service/services/src/main/java/at/tuwien/service/impl/TableServiceImpl.java @@ -36,50 +36,52 @@ public class TableServiceImpl extends HibernateConnector implements TableService private final UserService userService; private final TableRepository tableRepository; private final DatabaseService databaseService; - private final ContainerService containerService; @Autowired public TableServiceImpl(TableMapper tableMapper, UserService userService, TableRepository tableRepository, - DatabaseService databaseService, ContainerService containerService) { + DatabaseService databaseService) { this.tableMapper = tableMapper; this.userService = userService; this.tableRepository = tableRepository; this.databaseService = databaseService; - this.containerService = containerService; } @Override @Transactional(readOnly = true) public List<Table> findAll(Long containerId, Long databaseId, Principal principal) throws DatabaseNotFoundException { - final Database database = databaseService.findPublicOrMineById(databaseId, principal); + final Database database = databaseService.findPublicOrMineById(containerId, databaseId, principal); return tableRepository.findByDatabase(database); } @Override @Transactional public void deleteTable(Long containerId, Long databaseId, Long tableId, Principal principal) - throws TableNotFoundException, DatabaseNotFoundException, ImageNotSupportedException, - ContainerNotFoundException { + throws TableNotFoundException, DatabaseNotFoundException, ImageNotSupportedException, TableMalformedException { /* find */ - final Container container = containerService.find(containerId); - final Database database = databaseService.findPublicOrMineById(databaseId, principal); + final Database database = databaseService.findPublicOrMineById(containerId, databaseId, principal); final Table table = findById(containerId, databaseId, tableId, principal); /* run query */ final Session session = getCurrentSession(database.getContainer().getImage(), database.getContainer(), database); final Transaction transaction = session.beginTransaction(); - session.createSQLQuery(tableMapper.tableToDropTableRawQuery(table)); - transaction.commit(); - log.info("Deleted table with id {}", table.getId()); - log.debug("deleted table {}", table); + try { + session.createSQLQuery(tableMapper.tableToDropTableRawQuery(table)); + activeConnection(session); + transaction.commit(); + log.info("Deleted table with id {}", table.getId()); + log.debug("deleted table {}", table); + } catch (PersistenceException e) { + log.error("Failed to drop table with id {}", tableId); + log.debug("failed to drop table {}", table); + throw new TableMalformedException("Failed to drop table"); + } } @Override @Transactional(readOnly = true) public Table findById(Long containerId, Long databaseId, Long tableId, Principal principal) - throws TableNotFoundException, DatabaseNotFoundException, ContainerNotFoundException { - final Container container = containerService.find(containerId); - final Database database = databaseService.findPublicOrMineById(databaseId, principal); + throws TableNotFoundException, DatabaseNotFoundException { + final Database database = databaseService.findPublicOrMineById(containerId, databaseId, principal); final Optional<Table> optional = tableRepository.findByDatabaseAndId(database, tableId); if (optional.isEmpty()) { log.error("Failed to find table with id {} in metadata database", tableId); @@ -92,10 +94,9 @@ public class TableServiceImpl extends HibernateConnector implements TableService @Transactional public Table createTable(Long containerId, Long databaseId, TableCreateDto createDto, Principal principal) throws ImageNotSupportedException, DatabaseNotFoundException, TableMalformedException, - TableNameExistsException, ContainerNotFoundException, UserNotFoundException { + TableNameExistsException, UserNotFoundException { /* find */ - final Container container = containerService.find(containerId); - final Database database = databaseService.findPublicOrMineById(databaseId, principal); + final Database database = databaseService.findPublicOrMineById(containerId, databaseId, principal); final Optional<Table> optional = tableRepository.findByDatabaseAndInternalName(database, tableMapper.nameToInternalName(createDto.getName())); if (optional.isPresent()) { @@ -107,14 +108,14 @@ public class TableServiceImpl extends HibernateConnector implements TableService final Session session = getCurrentSession(database.getContainer().getImage(), database.getContainer(), database); final Transaction transaction = session.beginTransaction(); final CreateTableRawQuery query = tableMapper.tableToCreateTableRawQuery(database, createDto); - log.trace("create table raw query is [{}]", query); if (query.getGenerated()) { /* in case the id column needs to be generated, we need to generate the sequence too */ - try (session) { + try { session.createSQLQuery(tableMapper.tableToCreateSequenceRawQuery(database, createDto)) .executeUpdate(); } catch (PersistenceException e) { log.error("Table sequence exists, but table does not. Create an issue for this."); + session.close(); throw new TableNameExistsException("Sequence exists", e); } log.debug("created id sequence"); @@ -122,12 +123,15 @@ public class TableServiceImpl extends HibernateConnector implements TableService try { session.createSQLQuery(query.getQuery()) .executeUpdate(); + activeConnection(session); transaction.commit(); } catch (PersistenceException e) { log.error("Failed to create table"); log.debug("failed to create table: {}", e.getMessage()); + session.close(); throw new TableMalformedException("Failed to create table", e); } + session.close(); int[] idx = {0}; /* map table */ final Table tmp = tableMapper.tableCreateDtoToTable(createDto); @@ -152,16 +156,19 @@ public class TableServiceImpl extends HibernateConnector implements TableService }); /* create history view */ final Session session2 = getCurrentSession(database.getContainer().getImage(), database.getContainer(), database); - try (session2) { + try { final Transaction transaction2 = session2.beginTransaction(); session2.createSQLQuery(tableMapper.tableToCreateHistoryViewRawQuery(entity)) .executeUpdate(); + activeConnection(session2); transaction2.commit(); } catch (PersistenceException e) { log.error("Failed to create history view"); log.debug("failed to create history view: {}", e.getMessage()); + session2.close(); throw new TableMalformedException("Failed to create history view"); } + session2.close(); /* save */ final Table table = tableRepository.save(entity); log.info("Created table with id {}", table.getId()); diff --git a/fda-ui/components/QueryList.vue b/fda-ui/components/QueryList.vue index 248123b98fb16e961feed0245468b41bae9ac562..3f8ac4066302e91c886cc825f578cada68f1718a 100644 --- a/fda-ui/components/QueryList.vue +++ b/fda-ui/components/QueryList.vue @@ -11,9 +11,16 @@ <v-expansion-panel v-for="(item, i) in queries" :key="i" @click="details(item)"> <v-expansion-panel-header> <pre>{{ item.query }}</pre> - <v-icon v-if="item.identifier" color="primary" title="Persisted" class="pid-icon">mdi-lock-clock</v-icon> + <v-icon v-if="item.identifier" color="primary" title="Query with metadata" class="pid-icon">mdi-lock-clock</v-icon> + <v-icon v-if="erroneous(item)" color="error" title="Query failed to execute" class="pid-icon">mdi-flash</v-icon> </v-expansion-panel-header> <v-expansion-panel-content> + <v-alert + v-if="erroneous(item)" + border="left" + color="error"> + This query failed to execute and did not produce a subset. + </v-alert> <v-row dense> <v-col> <v-list dense> @@ -145,6 +152,9 @@ export default { this.$toast.error('Could not list queries.') } }, + erroneous (query) { + return !query.result_hash + }, details (query) { this.queryDetails = query } diff --git a/fda-ui/components/TableSchema.vue b/fda-ui/components/TableSchema.vue index 0474003d66d9d9ea68a9bfa4e84f0e11ac08286d..5d4577bc1c749a70f05ecae53ff63ddbebf3588e 100644 --- a/fda-ui/components/TableSchema.vue +++ b/fda-ui/components/TableSchema.vue @@ -118,8 +118,14 @@ export default { loading: false, dateFormats: [], valid: true, + error: false, finished: false, tableColumns: [], + container: { + image: { + id: null + } + }, columnTypes: [ // { value: 'ENUM', text: 'Enumeration' }, // Disabled for now, not implemented, #145 { value: 'boolean', text: 'Boolean' }, @@ -140,22 +146,35 @@ export default { } }, mounted () { - this.loadDateFormats() + this.loadContainer() + .then(() => this.loadImage()) }, methods: { - async loadDateFormats () { + async loadContainer () { const getUrl = `/api/container/${this.$route.params.container_id}` - let getResult try { this.loading = true - getResult = await this.$axios.get(getUrl) - this.dateFormats = getResult.data.image.date_formats + const res = await this.$axios.get(getUrl) + this.container = res.data + console.debug('retrieve container', this.container) + } catch (err) { + this.error = true + console.error('retrieve image date formats failed', err) + } + this.loading = false + }, + async loadImage () { + const getUrl = `/api/image/${this.container.image.id}` + try { + this.loading = true + const res = await this.$axios.get(getUrl) + this.dateFormats = res.data.date_formats console.debug('retrieve image date formats', this.dateFormats) - this.loading = false } catch (err) { - this.loading = false + this.error = true console.error('retrieve image date formats failed', err) } + this.loading = false }, submit () { this.finished = true diff --git a/fda-ui/components/dialogs/CreateDB.vue b/fda-ui/components/dialogs/CreateDB.vue index 799c934d0de38f3a69126af8c355f759b3f80929..d97ee9e875b525c683c673da9f27a99344d8de85 100644 --- a/fda-ui/components/dialogs/CreateDB.vue +++ b/fda-ui/components/dialogs/CreateDB.vue @@ -1,6 +1,7 @@ <template> <div> <v-form ref="form" v-model="valid" @submit.prevent="submit"> + <v-progress-linear v-if="loading" v-model="progress" :color="loadingColor" /> <v-card> <v-card-title> Create Database @@ -77,6 +78,7 @@ export default { error: false, engine: null, engines: [], + progress: 0, createContainer: { name: null, repository: null, @@ -154,7 +156,7 @@ export default { res = await this.$axios.post('/api/container', this.createContainer, this.config) containerId = res.data.id console.debug('created container', res.data) - this.loading = false + this.progress = 25 } catch (err) { this.error = true this.loading = false @@ -174,6 +176,7 @@ export default { this.error = false res = await this.$axios.put(`/api/container/${containerId}`, { action: 'start' }, this.config) console.debug('started container', res.data) + this.progress = 50 } catch (err) { this.error = true this.$toast.error('Could not start container.') @@ -195,6 +198,7 @@ export default { break } catch (err) { console.debug('wait', res) + this.progress += 10 await this.sleep(3000) } } @@ -204,6 +208,7 @@ export default { this.$toast.error('Could not create database.') return } + this.progress = 100 this.loading = false this.$toast.success(`Database "${res.data.name}" created.`) this.$emit('close') diff --git a/fda-ui/components/dialogs/PersistQuery.vue b/fda-ui/components/dialogs/PersistQuery.vue index 507ccf5a9927f92014bf260e1d7022cc6bdcae0d..b6d7e2524f29a9cc267f7bda55636690e5c7bcac 100644 --- a/fda-ui/components/dialogs/PersistQuery.vue +++ b/fda-ui/components/dialogs/PersistQuery.vue @@ -273,11 +273,15 @@ export default { this.$emit('close', { action: 'closed' }) }, validateOrcid (orcid) { + if (!orcid || orcid.length === 0) { + return true + } return isValidOrcid(orcid) }, addCreatorSelf () { if (!this.user.firstname || !this.user.lastname) { this.addCreator() + return } this.identifier.creators.push({ name: `${this.user.lastname}, ${this.user.firstname}`, diff --git a/fda-ui/components/query/Builder.vue b/fda-ui/components/query/Builder.vue index ff784984cfa7600a667c18f5b4dc1bad6c0eb9a8..fb47f23645e4277b228e99f16c7527c8a49ec2d3 100644 --- a/fda-ui/components/query/Builder.vue +++ b/fda-ui/components/query/Builder.vue @@ -71,23 +71,17 @@ class="mt-2 ml-3" /> </v-tab-item> </v-tabs-items> - </v-card> - <v-card flat> - <v-card-text> + <v-card-text v-if="queryId"> <v-row> - <v-col> - <QueryResults ref="queryResults" v-model="queryId" /> - </v-col> - </v-row> - <v-row v-if="queryId"> <v-col> <v-btn color="blue-grey white--text" :to="`/container/${$route.params.container_id}/database/${databaseId}/query/${queryId}`"> - More + View </v-btn> </v-col> </v-row> </v-card-text> </v-card> + <QueryResults ref="queryResults" v-model="queryId" /> </div> </template> diff --git a/fda-ui/pages/container/_container_id/database/_database_id/query/_query_id/index.vue b/fda-ui/pages/container/_container_id/database/_database_id/query/_query_id/index.vue index b7ef2bdbad23610c8f746cf20035012860e92add..456c774cdb992ff0607feba958e49ae7b3a9b8a6 100644 --- a/fda-ui/pages/container/_container_id/database/_database_id/query/_query_id/index.vue +++ b/fda-ui/pages/container/_container_id/database/_database_id/query/_query_id/index.vue @@ -8,7 +8,7 @@ </v-toolbar-title> <v-spacer /> <v-toolbar-title> - <v-btn v-if="!identifier.id && !loadingIdentifier" color="secondary" class="mr-2" :disabled="error || !executionUTC || !token" @click.stop="openDialog()"> + <v-btn v-if="!identifier.id && !loadingIdentifier" color="secondary" class="mr-2" :disabled="error || erroneous || !executionUTC || !token" @click.stop="openDialog()"> <v-icon left>mdi-content-save-outline</v-icon> Save </v-btn> <v-btn v-if="result_visibility" :disabled="error" color="primary" :loading="downloadLoading" @click.stop="download"> @@ -176,9 +176,9 @@ </v-list-item> <v-list-item> <v-list-item-icon> - <v-icon :color="result_visibility_icon ? 'success' : 'error'">mdi-table</v-icon> + <v-icon :color="result_visibility_icon ? 'success' : 'error'">{{ result_icon }}</v-icon> </v-list-item-icon> - <v-list-item-content> + <v-list-item-content v-if="!erroneous"> <v-list-item-title> Result Visibility </v-list-item-title> @@ -201,13 +201,30 @@ <span v-if="!metadataLoading">{{ result_number }}</span> </v-list-item-content> </v-list-item-content> + <v-list-item-content v-if="erroneous"> + <v-list-item-title> + Result Visibility + </v-list-item-title> + <v-list-item-content> + <v-alert + v-if="!loadingQuery && erroneous" + border="left" + color="error"> + This query failed to execute and did not produce a subset. + </v-alert> + </v-list-item-content> + </v-list-item-content> </v-list-item> </v-list> </v-card-text> - <v-card-text> - <QueryResults id="query-results" ref="queryResults" v-model="query.id" :query-id="query.id" class="mt-0 mb-0" /> - </v-card-text> </v-card> + <QueryResults + v-if="!erroneous" + id="query-results" + ref="queryResults" + v-model="query.id" + :query-id="query.id" + class="mt-0 mb-0" /> <v-breadcrumbs :items="items" class="pa-0 mt-2" /> <v-dialog v-model="persistQueryDialog" @@ -286,7 +303,7 @@ export default { persistQueryDialog: false, loadingDatabase: false, loadingIdentifier: false, - loadingQuery: false, + loadingQuery: true, metadataLoading: false, downloadLoading: false, error: false, @@ -297,6 +314,9 @@ export default { token () { return this.$store.state.token }, + result_icon () { + return this.erroneous && !this.loadingQuery ? 'mdi-flash' : 'mdi-table' + }, baseUrl () { return 'http://' + location.host }, @@ -327,6 +347,9 @@ export default { return this.database.is_public }, result_visibility () { + if (this.erroneous) { + return false + } if (this.database.is_public) { return true } @@ -336,6 +359,9 @@ export default { return this.identifier.visibility === 'everyone' }, result_visibility_icon () { + if (this.erroneous) { + return false + } if (this.database.is_public) { return true } @@ -364,6 +390,9 @@ export default { }, creators () { return this.identifier.id ? this.identifier.creators : null + }, + erroneous () { + return !this.query.result_hash } }, mounted () { diff --git a/fda-ui/pages/container/_container_id/database/_database_id/table/_table_id/index.vue b/fda-ui/pages/container/_container_id/database/_database_id/table/_table_id/index.vue index 60b75e38f6a124772dcf74596b50a2358d037fae..df45aaa9c2049b173a3668ce9876ceca3e66d6ba 100644 --- a/fda-ui/pages/container/_container_id/database/_database_id/table/_table_id/index.vue +++ b/fda-ui/pages/container/_container_id/database/_database_id/table/_table_id/index.vue @@ -15,19 +15,25 @@ <v-btn color="primary" :disabled="!token" class="mr-2" @click="addTuple"> <v-icon left>mdi-plus</v-icon> Add </v-btn> - <v-btn v-if="canEdit" :disabled="!token" color="warn" class="mr-2" @click="editTupleDialog = true"> + <v-btn v-if="canEdit" :disabled="!token" color="warn" class="mr-2 mb-1" @click="editTupleDialog = true"> <v-icon left>mdi-pencil</v-icon> Edit </v-btn> - <v-btn v-if="canDelete" :disabled="!token" color="error" class="mr-2" @click="deleteItems"> + <v-btn v-if="canDelete" :disabled="!token" color="error" class="mr-2 mb-1" @click="deleteItems"> <v-icon left>mdi-delete</v-icon> Delete<span v-if="selection.length > 1"> {{ selection.length }}</span> </v-btn> - <v-btn :disabled="!token" :to="`/container/${$route.params.container_id}/database/${$route.params.database_id}/query/create?tid=${$route.params.table_id}`" color="secondary" class="mr-2" @click="deleteItems"> + <v-btn :disabled="!token" :to="`/container/${$route.params.container_id}/database/${$route.params.database_id}/query/create?tid=${$route.params.table_id}`" color="secondary" class="mr-2 mb-1" @click="deleteItems"> <v-icon left>mdi-wrench</v-icon> Create Subset </v-btn> - <v-btn :disabled="!token" :to="`/container/${$route.params.container_id}/database/${$route.params.database_id}/table/${$route.params.table_id}/import`"> + <v-btn :disabled="!token" class="mb-1" :to="`/container/${$route.params.container_id}/database/${$route.params.database_id}/table/${$route.params.table_id}/import`"> <v-icon left>mdi-cloud-upload</v-icon> Import csv </v-btn> - <v-btn v-if="false" color="primary" :disabled="!token" :href="`/api/container/${$route.params.container_id}/database/${$route.params.database_id}/table/${$route.params.table_id}/data/export`" target="_blank"> + <v-btn + v-if="false" + color="primary" + class="mb-1" + :disabled="!token" + :href="`/api/container/${$route.params.container_id}/database/${$route.params.database_id}/table/${$route.params.table_id}/data/export`" + target="_blank"> <v-icon left>mdi-download</v-icon> Download </v-btn> </v-toolbar-title> diff --git a/fda-ui/pages/container/index.vue b/fda-ui/pages/container/index.vue index 0719935b3007a024909f9550f1a241dbe20c3fb6..ab3e7495d41acd87d006a2245be7cba31cebf512 100644 --- a/fda-ui/pages/container/index.vue +++ b/fda-ui/pages/container/index.vue @@ -74,6 +74,7 @@ export default { return { createDbDialog: false, containers: [], + searchQuery: null, items: [ { text: 'Databases', to: '/container', activeClass: '' } ], @@ -107,6 +108,9 @@ export default { formatCreator (creator) { return formatUser(creator) }, + search () { + console.debug('search for', this.searchQuery) + }, async loadContainers () { this.createDbDialog = false try { diff --git a/fda-ui/pages/user/index.vue b/fda-ui/pages/user/index.vue index 85cf94f44da9f32271aa553d4163e5e6e25e635e..89747447928482575782806b6123304e131a3af7 100644 --- a/fda-ui/pages/user/index.vue +++ b/fda-ui/pages/user/index.vue @@ -19,7 +19,7 @@ <v-col cols="5"> <v-text-field v-model="user.email" - :disabled="user.email_verified" + :disabled="user.email_verified || error" :rules="[v => !!v || $t('Required')]" required label="E-Mail Address *" /> @@ -28,7 +28,7 @@ <v-row dense> <v-col cols="5"> <v-btn - :disabled="user.email_verified" + :disabled="user.email_verified || error" color="secondary" type="submit" @click="resend"> @@ -60,6 +60,7 @@ <v-col cols="5"> <v-text-field v-model="user.titles_before" + :disabled="error" hint="e.g. Prof." label="Titles Before" /> </v-col> @@ -68,6 +69,7 @@ <v-col cols="5"> <v-text-field v-model="user.firstname" + :disabled="error" :rules="[v => !!v || $t('Required')]" required label="Firstname *" /> @@ -77,6 +79,7 @@ <v-col cols="5"> <v-text-field v-model="user.lastname" + :disabled="error" :rules="[v => !!v || $t('Required')]" required label="Lastname *" /> @@ -86,6 +89,7 @@ <v-col cols="5"> <v-text-field v-model="user.titles_after" + :disabled="error" hint="e.g. BSc" label="Titles After" /> </v-col> @@ -94,6 +98,7 @@ <v-col cols="5"> <v-text-field v-model="user.affiliation" + :disabled="error" hint="e.g. University of xyz" label="Affiliation" /> </v-col> @@ -102,6 +107,7 @@ <v-col cols="5"> <v-text-field v-model="user.orcid" + :disabled="error" :rules="[v => validateOrcid(v) || $t('Invalid ORCID')]" maxlength="19" hint="e.g. 0000-0002-1825-0097" @@ -112,7 +118,7 @@ <v-col cols="5"> <v-btn color="primary" - :disabled="!valid2" + :disabled="!valid2 || error" type="submit" @click="updateInfo"> Update @@ -129,6 +135,7 @@ <v-col cols="5"> <v-text-field v-model="reset.password" + :disabled="error" type="password" :rules="[v => !!v || $t('Required')]" required @@ -139,7 +146,7 @@ <v-col cols="5"> <v-btn color="primary" - :disabled="!valid3" + :disabled="!valid3 || error" type="submit" @click="changePassword"> Change @@ -229,6 +236,7 @@ export default { console.debug('user', this.user) this.error = false } catch (err) { + this.$toast.error('Failed to load user') console.error('user', err) this.error = true }