diff --git a/fda-database-service/pom.xml b/fda-database-service/pom.xml index 9a00121bbed3ad330e9c0d8a6734ba5dc4a8e497..56681cae00cb2bb4b63fff62a567366079f0cd40 100644 --- a/fda-database-service/pom.xml +++ b/fda-database-service/pom.xml @@ -178,15 +178,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-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 b12060796c82d2dc1469e78b0db26a4442cf60c4..fea63c6d1f19540efee2f2deac7ec809a67db888 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 @@ -72,8 +72,8 @@ public class ContainerDatabaseEndpoint { @PreAuthorize("hasRole('ROLE_RESEARCHER')") @Operation(summary = "Create database", security = @SecurityRequirement(name = "bearerAuth")) public ResponseEntity<DatabaseBriefDto> create(@NotBlank @PathVariable("id") Long id, - @Valid @RequestBody DatabaseCreateDto createDto, - Principal principal) + @Valid @RequestBody DatabaseCreateDto createDto, + Principal principal) throws ImageNotSupportedException, ContainerNotFoundException, DatabaseMalformedException, AmqpException, ContainerConnectionException, UserNotFoundException { final Database database = databaseService.create(id, createDto, principal); @@ -87,8 +87,8 @@ public class ContainerDatabaseEndpoint { @PreAuthorize("hasRole('ROLE_RESEARCHER')") @Operation(summary = "Update database", security = @SecurityRequirement(name = "bearerAuth")) public ResponseEntity<DatabaseBriefDto> update(@NotBlank @PathVariable("id") Long id, - @NotBlank @PathVariable Long databaseId, - @Valid @RequestBody DatabaseModifyDto modifyDto) + @NotBlank @PathVariable Long databaseId, + @Valid @RequestBody DatabaseModifyDto modifyDto) throws UserNotFoundException, DatabaseNotFoundException, LicenseNotFoundException { final Database database = databaseService.modify(id, databaseId, modifyDto); return ResponseEntity.status(HttpStatus.ACCEPTED) @@ -122,7 +122,8 @@ public class ContainerDatabaseEndpoint { public ResponseEntity<?> delete(@NotBlank @PathVariable("id") Long containerId, @NotBlank @PathVariable Long databaseId, Principal principal) throws DatabaseNotFoundException, - ImageNotSupportedException, DatabaseMalformedException, AmqpException, ContainerConnectionException { + ImageNotSupportedException, DatabaseMalformedException, AmqpException, ContainerConnectionException, + ContainerNotFoundException { final Database database = databaseService.findById(containerId, databaseId); messageQueueService.deleteExchange(database); databaseService.delete(containerId, databaseId, principal); 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 new file mode 100644 index 0000000000000000000000000000000000000000..9193ed80caa051c2f993db9124c2cbad1f69a01a --- /dev/null +++ b/fda-database-service/rest-service/src/main/resources/mariadb_hibernate.cfg.xml @@ -0,0 +1,17 @@ +<?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">5</property> + <property name="c3p0.max_size">30</property> + <property name="c3p0.acquire_increment">5</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> + </session-factory> +</hibernate-configuration> diff --git a/fda-database-service/services/src/main/java/at/tuwien/service/ContainerService.java b/fda-database-service/services/src/main/java/at/tuwien/service/ContainerService.java new file mode 100644 index 0000000000000000000000000000000000000000..ee70355c6d596650f7b0896dba30882cd760eeae --- /dev/null +++ b/fda-database-service/services/src/main/java/at/tuwien/service/ContainerService.java @@ -0,0 +1,8 @@ +package at.tuwien.service; + +import at.tuwien.entities.container.Container; +import at.tuwien.exception.ContainerNotFoundException; + +public interface ContainerService { + Container find(Long id) throws ContainerNotFoundException; +} 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 155946e442a2a6781eafe2471df0a386887c31d1..471b2c1f73efc931fdd0b4dd47292a4fa5720f18 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 @@ -63,7 +63,7 @@ public interface DatabaseService { */ void delete(Long id, Long databaseId, Principal principal) throws DatabaseNotFoundException, ImageNotSupportedException, - DatabaseMalformedException, AmqpException, ContainerConnectionException; + DatabaseMalformedException, AmqpException, ContainerConnectionException, ContainerNotFoundException; /** * Creates a new database with minimal metadata in the metadata database and creates a new database on the container. @@ -95,23 +95,4 @@ public interface DatabaseService { */ Database modify(Long id, Long databaseId, DatabaseModifyDto modifyDto) throws UserNotFoundException, DatabaseNotFoundException, LicenseNotFoundException; - - /** - * Returns a new session for a given {@link Database} entity. - * - * @param database The database entity. - * @return A new session if successful. - * @throws ContainerConnectionException The container is not reachable from the database service. - * @throws DatabaseMalformedException The database is malformed e.g. a session can be created. - */ - Session getSession(Database database) throws ContainerConnectionException, DatabaseMalformedException; - - /** - * Returns a new transaction for a given session. - * - * @param session The session. - * @return The transaction if successful. - * @throws ContainerConnectionException When no connection to the remote database fails (e.g. the container is not running). - */ - Transaction getTransaction(Session session) throws ContainerConnectionException; } diff --git a/fda-database-service/services/src/main/java/at/tuwien/service/impl/ContainerServiceImpl.java b/fda-database-service/services/src/main/java/at/tuwien/service/impl/ContainerServiceImpl.java new file mode 100644 index 0000000000000000000000000000000000000000..bb657b1812fc208a85020c84a28856c084273ac6 --- /dev/null +++ b/fda-database-service/services/src/main/java/at/tuwien/service/impl/ContainerServiceImpl.java @@ -0,0 +1,33 @@ +package at.tuwien.service.impl; + +import at.tuwien.entities.container.Container; +import at.tuwien.exception.ContainerNotFoundException; +import at.tuwien.repository.jpa.ContainerRepository; +import at.tuwien.service.ContainerService; +import lombok.extern.log4j.Log4j2; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; + +import java.util.Optional; + +@Log4j2 +@Service +public class ContainerServiceImpl implements ContainerService { + + private final ContainerRepository containerRepository; + + @Autowired + public ContainerServiceImpl(ContainerRepository containerRepository) { + this.containerRepository = containerRepository; + } + + @Override + public Container find(Long id) throws ContainerNotFoundException { + final Optional<Container> optional = containerRepository.findById(id); + if (optional.isEmpty()) { + log.error("Failed to find container with id {}", id); + throw new ContainerNotFoundException("Failed to find container"); + } + return optional.get(); + } +} 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 05e6c8ffe1840586bfabdf92c43dfd3439632c1e..d958b7381a1c12c14967466323dbcbc06327f158 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 @@ -1,9 +1,12 @@ 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 lombok.extern.log4j.Log4j2; +import org.hibernate.Session; import org.hibernate.SessionFactory; import org.hibernate.cfg.Configuration; import org.springframework.stereotype.Service; @@ -15,37 +18,35 @@ import java.util.stream.Collectors; @Service public abstract class HibernateConnector { - private static final Integer MIN_SIZE = 5; - private static final Integer MAX_SIZE = 30; - 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"; - - @Transactional - protected SessionFactory getSessionFactory(Database database) { - final String url = "jdbc:" + database.getContainer().getImage().getJdbcMethod() + "://" + database.getContainer().getInternalName(); - log.trace("hibernate jdbc url '{}'", url); - final String password = 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(ContainerImageEnvironmentItemType.PRIVILEGED_USERNAME)) + .map(ContainerImageEnvironmentItem::getValue) + .collect(Collectors.toList()) + .get(0); + final String password = image.getEnvironment() .stream() - .filter(e -> e.getType().equals(ContainerImageEnvironmentItemType.PASSWORD)) + .filter(e -> e.getType().equals(ContainerImageEnvironmentItemType.PRIVILEGED_PASSWORD)) .map(ContainerImageEnvironmentItem::getValue) .collect(Collectors.toList()) .get(0); - final Configuration configuration = new Configuration() - .setProperty("hibernate.connection.url", url) - .setProperty("hibernate.connection.username", "root") - .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.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)); - return configuration.buildSessionFactory(); + + 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.warn("Session is closed, opening..."); + session = sessionFactory.openSession(); + } + return session; } 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 d676d33b979a91994aeec2b3653e5fc83f11f34e..7d21a2d923b4748acd04242f1d18001e5bdb6833 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 @@ -4,7 +4,6 @@ import at.tuwien.api.database.DatabaseCreateDto; import at.tuwien.api.database.DatabaseModifyDto; import at.tuwien.entities.container.Container; import at.tuwien.entities.database.Database; -import at.tuwien.entities.database.LanguageType; import at.tuwien.entities.database.License; import at.tuwien.entities.user.User; import at.tuwien.exception.*; @@ -13,15 +12,13 @@ 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; import at.tuwien.service.DatabaseService; import at.tuwien.service.LicenseService; import at.tuwien.service.UserService; import lombok.extern.log4j.Log4j2; -import org.hibernate.HibernateException; import org.hibernate.Session; -import org.hibernate.SessionFactory; import org.hibernate.Transaction; -import org.hibernate.exception.GenericJDBCException; import org.hibernate.query.NativeQuery; import org.hibernate.service.spi.ServiceException; import org.springframework.beans.factory.annotation.Autowired; @@ -44,6 +41,7 @@ public class MariaDbServiceImpl extends HibernateConnector implements DatabaseSe 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; @@ -51,13 +49,14 @@ public class MariaDbServiceImpl extends HibernateConnector implements DatabaseSe @Autowired public MariaDbServiceImpl(AmqpMapper amqpMapper, UserService userService, LicenseService licenseService, DatabaseMapper databaseMapper, RabbitMqServiceImpl amqpService, - DatabaseRepository databaseRepository, ContainerRepository containerRepository, - DatabaseidxRepository databaseidxRepository) { + ContainerService containerService, DatabaseRepository databaseRepository, + ContainerRepository containerRepository, 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; @@ -107,23 +106,24 @@ public class MariaDbServiceImpl extends HibernateConnector implements DatabaseSe @Override @Transactional public void delete(Long containerId, Long databaseId, Principal principal) throws DatabaseNotFoundException, - ImageNotSupportedException, DatabaseMalformedException, ContainerConnectionException, AmqpException { + ImageNotSupportedException, DatabaseMalformedException, ContainerConnectionException, AmqpException, + ContainerNotFoundException { + final Container container = containerService.find(containerId); final Database database = findPublicOrMineById(containerId, databaseId, principal); if (!database.getContainer().getImage().getRepository().equals("mariadb")) { throw new ImageNotSupportedException("Currently only MariaDB is supported"); } /* run query */ - final Session session = getSession(database); - final Transaction transaction = getTransaction(session); + final Session session = getCurrentSession(container.getImage(), container, database); + final Transaction transaction = session.beginTransaction(); final NativeQuery<?> query = session.createSQLQuery(databaseMapper.databaseToRawDeleteDatabaseQuery(database)); - try { + try (session) { log.debug("query affected {} rows", query.executeUpdate()); + transaction.commit(); } catch (ServiceException e) { log.error("Failed to delete database."); throw new DatabaseMalformedException("Failed to delete database", e); } - transaction.commit(); - session.close(); database.setDeleted(Instant.now()) /* method has void, only for debug logs */; /* save in metadata database */ databaseRepository.deleteById(databaseId); @@ -135,38 +135,34 @@ public class MariaDbServiceImpl extends HibernateConnector implements DatabaseSe @Override @Transactional - public Database create(Long id, DatabaseCreateDto createDto, Principal principal) + public Database create(Long containerId, DatabaseCreateDto createDto, Principal principal) throws ImageNotSupportedException, ContainerNotFoundException, DatabaseMalformedException, AmqpException, ContainerConnectionException, UserNotFoundException { - final Optional<Container> container = containerRepository.findById(id); - if (container.isEmpty()) { - log.warn("Container with id {} does not exist", id); - throw new ContainerNotFoundException("Container does not exist."); - } + final Container container = containerService.find(containerId); /* start the object */ final Database database = new Database(); database.setName(createDto.getName()); database.setInternalName(databaseMapper.nameToInternalName(database.getName())); - database.setContainer(container.get()); + database.setContainer(container); /* run query */ - final Session session = getSession(database); - final Transaction transaction = getTransaction(session); + final Session session = getCurrentSession(container.getImage(), container, database); + 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."); + session.close(); throw new DatabaseMalformedException("Failed to delete database", e); } final NativeQuery<?> grant = session.createSQLQuery(databaseMapper.imageToRawGrantReadonlyAccessQuery()); - try { + try (session) { log.debug("grant affected {} rows", grant.executeUpdate()); + transaction.commit(); } catch (PersistenceException e) { log.error("Failed to grant privileges."); throw new DatabaseMalformedException("Failed to grant privileges", e); } - transaction.commit(); - session.close(); /* save in metadata database */ database.setExchange(amqpMapper.exchangeName(database)); database.setDescription(createDto.getDescription()); @@ -207,36 +203,4 @@ public class MariaDbServiceImpl extends HibernateConnector implements DatabaseSe return out; } - @Override - public Session getSession(Database database) throws ContainerConnectionException, DatabaseMalformedException { - final SessionFactory factory; - try { - factory = getSessionFactory(database); - } catch (HibernateException e) { - log.error("Connection failed: {}", e.getMessage()); - log.throwing(e); - throw new ContainerConnectionException("Connection failed", e); - } - final Session session; - try { - session = factory.openSession(); - } catch (HibernateException e) { - log.error("Session failed"); - throw new DatabaseMalformedException("Session failed", e); - } - return session; - } - - @Override - public Transaction getTransaction(Session session) throws ContainerConnectionException { - final Transaction transaction; - try { - transaction = session.beginTransaction(); - } catch (GenericJDBCException e) { - log.error("Failed to begin transaction"); - throw new ContainerConnectionException("Failed to begin transaction", e); - } - return transaction; - } - }