/* * Copyright (c) 2012-2018 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Red Hat, Inc. - initial API and implementation */ import static org.eclipse.persistence.config.PersistenceUnitProperties.JDBC_DRIVER; import static org.eclipse.persistence.config.PersistenceUnitProperties.JDBC_PASSWORD; import static org.eclipse.persistence.config.PersistenceUnitProperties.JDBC_URL; import static org.eclipse.persistence.config.PersistenceUnitProperties.JDBC_USER; import static org.eclipse.persistence.config.PersistenceUnitProperties.TRANSACTION_TYPE; import com.google.inject.Singleton; import com.google.inject.TypeLiteral; import com.google.inject.multibindings.Multibinder; import com.google.inject.name.Names; import com.google.inject.persist.Transactional; import com.google.inject.persist.UnitOfWork; import com.google.inject.persist.jpa.JpaPersistModule; import java.sql.Connection; import java.sql.DriverManager; import java.sql.SQLException; import java.util.ArrayList; import java.util.Collection; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.concurrent.TimeUnit; import javax.inject.Inject; import javax.inject.Provider; import javax.persistence.EntityManager; import javax.persistence.NoResultException; import javax.persistence.spi.PersistenceUnitTransactionType; import org.eclipse.che.account.spi.AccountImpl; import org.eclipse.che.api.user.server.model.impl.UserImpl; import org.eclipse.che.api.workspace.server.model.impl.WorkspaceImpl; import org.eclipse.che.api.workspace.server.model.impl.stack.StackImpl; import org.eclipse.che.commons.test.tck.JpaCleaner; import org.eclipse.che.commons.test.tck.TckModule; import org.eclipse.che.commons.test.tck.TckResourcesCleaner; import org.eclipse.che.commons.test.tck.repository.JpaTckRepository; import org.eclipse.che.commons.test.tck.repository.TckRepository; import org.eclipse.che.commons.test.tck.repository.TckRepositoryException; import org.eclipse.che.core.db.DBInitializer; import org.eclipse.che.core.db.schema.SchemaInitializer; import org.eclipse.che.core.db.schema.impl.flyway.FlywaySchemaInitializer; import org.eclipse.che.multiuser.api.permission.server.AbstractPermissionsDomain; import org.eclipse.che.multiuser.api.permission.server.SystemDomain; import org.eclipse.che.multiuser.api.permission.server.jpa.JpaSystemPermissionsDao; import org.eclipse.che.multiuser.api.permission.server.model.impl.SystemPermissionsImpl; import org.eclipse.che.multiuser.api.permission.server.spi.PermissionsDao; import org.eclipse.che.multiuser.api.permission.server.spi.tck.SystemPermissionsDaoTest; import org.eclipse.che.multiuser.machine.authentication.server.signature.jpa.JpaSignatureKeyDao; import org.eclipse.che.multiuser.machine.authentication.server.signature.model.impl.SignatureKeyPairImpl; import org.eclipse.che.multiuser.machine.authentication.server.signature.spi.SignatureKeyDao; import org.eclipse.che.multiuser.organization.api.permissions.OrganizationDomain; import org.eclipse.che.multiuser.organization.spi.MemberDao; import org.eclipse.che.multiuser.organization.spi.OrganizationDao; import org.eclipse.che.multiuser.organization.spi.OrganizationDistributedResourcesDao; import org.eclipse.che.multiuser.organization.spi.impl.MemberImpl; import org.eclipse.che.multiuser.organization.spi.impl.OrganizationDistributedResourcesImpl; import org.eclipse.che.multiuser.organization.spi.impl.OrganizationImpl; import org.eclipse.che.multiuser.organization.spi.jpa.JpaMemberDao; import org.eclipse.che.multiuser.organization.spi.jpa.JpaOrganizationDao; import org.eclipse.che.multiuser.organization.spi.jpa.JpaOrganizationDistributedResourcesDao; import org.eclipse.che.multiuser.permission.workspace.server.model.impl.WorkerImpl; import org.eclipse.che.multiuser.permission.workspace.server.spi.WorkerDao; import org.eclipse.che.multiuser.permission.workspace.server.spi.jpa.JpaStackPermissionsDao; import org.eclipse.che.multiuser.permission.workspace.server.spi.jpa.JpaWorkerDao; import org.eclipse.che.multiuser.permission.workspace.server.spi.tck.StackPermissionsDaoTest; import org.eclipse.che.multiuser.permission.workspace.server.spi.tck.WorkerDaoTest; import org.eclipse.che.multiuser.permission.workspace.server.stack.StackPermissionsImpl; import org.eclipse.che.multiuser.resource.spi.FreeResourcesLimitDao; import org.eclipse.che.multiuser.resource.spi.impl.FreeResourcesLimitImpl; import org.eclipse.che.multiuser.resource.spi.jpa.JpaFreeResourcesLimitDao; import org.eclipse.che.security.PasswordEncryptor; import org.eclipse.che.security.SHA512PasswordEncryptor; import org.postgresql.ds.PGSimpleDataSource; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * Module for testing JPA DAO. * * @author Mihail Kuznyetsov */ public class MultiuserPostgresqlTckModule extends TckModule { private static final Logger LOG = LoggerFactory.getLogger(MultiuserPostgresqlTckModule.class); @Override protected void configure() { final Map properties = new HashMap<>(); properties.put(TRANSACTION_TYPE, PersistenceUnitTransactionType.RESOURCE_LOCAL.name()); final String dbUrl = System.getProperty("jdbc.url"); final String dbUser = System.getProperty("jdbc.user"); final String dbPassword = System.getProperty("jdbc.password"); waitConnectionIsEstablished(dbUrl, dbUser, dbPassword); properties.put(JDBC_URL, dbUrl); properties.put(JDBC_USER, dbUser); properties.put(JDBC_PASSWORD, dbPassword); properties.put(JDBC_DRIVER, System.getProperty("jdbc.driver")); JpaPersistModule main = new JpaPersistModule("main"); main.properties(properties); install(main); final PGSimpleDataSource dataSource = new PGSimpleDataSource(); dataSource.setUser(dbUser); dataSource.setPassword(dbPassword); dataSource.setUrl(dbUrl); bind(SchemaInitializer.class).toInstance(new FlywaySchemaInitializer(dataSource, "che-schema")); bind(DBInitializer.class).asEagerSingleton(); bind(TckResourcesCleaner.class).to(JpaCleaner.class); // repositories // api-account bind(new TypeLiteral>() {}) .toInstance(new JpaTckRepository<>(AccountImpl.class)); // api-user bind(new TypeLiteral>() {}).to(UserJpaTckRepository.class); // api-workspace bind(new TypeLiteral>() {}) .toInstance(new JpaTckRepository<>(WorkspaceImpl.class)); bind(new TypeLiteral>() {}) .toInstance(new JpaTckRepository<>(StackImpl.class)); bind(new TypeLiteral>() {}) .toInstance(new JpaTckRepository<>(WorkerImpl.class)); // api permission bind(new TypeLiteral>() {}) .toInstance(new JpaTckRepository<>(StackPermissionsImpl.class)); bind(new TypeLiteral>() {}) .toInstance(new JpaTckRepository<>(SystemPermissionsImpl.class)); bind(new TypeLiteral>() {}) .to(JpaStackPermissionsDao.class); bind(new TypeLiteral>() {}) .to(JpaSystemPermissionsDao.class); bind(new TypeLiteral>() {}) .to(StackPermissionsDaoTest.TestDomain.class); bind(new TypeLiteral>() {}) .to(WorkerDaoTest.TestDomain.class); bind(new TypeLiteral>() {}) .to(SystemPermissionsDaoTest.TestDomain.class); // api-organization bind(new TypeLiteral>() {}) .to(JpaOrganizationImplTckRepository.class); bind(new TypeLiteral>() {}) .toInstance(new JpaTckRepository<>(MemberImpl.class)); bind(new TypeLiteral>() {}) .toInstance(new JpaTckRepository<>(OrganizationDistributedResourcesImpl.class)); // api-resource bind(new TypeLiteral>() {}) .toInstance(new JpaTckRepository<>(FreeResourcesLimitImpl.class)); // machine token keys bind(new TypeLiteral>() {}) .toInstance(new JpaTckRepository<>(SignatureKeyPairImpl.class)); // dao bind(OrganizationDao.class).to(JpaOrganizationDao.class); bind(OrganizationDistributedResourcesDao.class) .to(JpaOrganizationDistributedResourcesDao.class); bind(FreeResourcesLimitDao.class).to(JpaFreeResourcesLimitDao.class); bind(WorkerDao.class).to(JpaWorkerDao.class); bind(MemberDao.class).to(JpaMemberDao.class); bind(SignatureKeyDao.class).to(JpaSignatureKeyDao.class); bind(new TypeLiteral>() {}).to(JpaMemberDao.class); bind(new TypeLiteral>() {}).to(OrganizationDomain.class); // SHA-512 ecnryptor is faster than PBKDF2 so it is better for testing bind(PasswordEncryptor.class).to(SHA512PasswordEncryptor.class).in(Singleton.class); // Creates empty multibinder to avoid error during container starting Multibinder.newSetBinder( binder(), String.class, Names.named(SystemDomain.SYSTEM_DOMAIN_ACTIONS)); } private static void waitConnectionIsEstablished(String dbUrl, String dbUser, String dbPassword) { boolean isAvailable = false; for (int i = 0; i < 20 && !isAvailable; i++) { try (Connection conn = DriverManager.getConnection(dbUrl, dbUser, dbPassword)) { isAvailable = true; } catch (SQLException x) { LOG.warn( "An attempt to connect to the database failed with an error: {}", x.getLocalizedMessage()); try { TimeUnit.MILLISECONDS.sleep(500); } catch (InterruptedException interruptedX) { throw new RuntimeException(interruptedX.getLocalizedMessage(), interruptedX); } } } if (!isAvailable) { throw new IllegalStateException("Couldn't initialize connection with a database"); } } @Transactional public static class UserJpaTckRepository implements TckRepository { @Inject private Provider managerProvider; @Inject private PasswordEncryptor encryptor; @Override public void createAll(Collection entities) throws TckRepositoryException { final EntityManager manager = managerProvider.get(); entities .stream() .map( user -> new UserImpl( user.getId(), user.getEmail(), user.getName(), user.getPassword() != null ? encryptor.encrypt(user.getPassword()) : null, user.getAliases())) .forEach(manager::persist); } @Override public void removeAll() throws TckRepositoryException { final EntityManager manager = managerProvider.get(); manager .createQuery("SELECT users FROM Usr users", UserImpl.class) .getResultList() .forEach(manager::remove); } } /** * Organizations require to have own repository because it is important to delete organization in * reverse order that they were stored. It allows to resolve problems with removing * suborganization before parent organization removing. * * @author Sergii Leschenko */ public static class JpaOrganizationImplTckRepository extends JpaTckRepository { @Inject protected Provider managerProvider; @Inject protected UnitOfWork uow; private final List createdOrganizations = new ArrayList<>(); public JpaOrganizationImplTckRepository() { super(OrganizationImpl.class); } @Override public void createAll(Collection entities) throws TckRepositoryException { super.createAll(entities); // It's important to save organization to remove them in the reverse order createdOrganizations.addAll(entities); } @Override public void removeAll() throws TckRepositoryException { uow.begin(); final EntityManager manager = managerProvider.get(); try { manager.getTransaction().begin(); for (int i = createdOrganizations.size() - 1; i > -1; i--) { // The query 'DELETE FROM ....' won't be correct as it will ignore orphanRemoval // and may also ignore some configuration options, while EntityManager#remove won't try { final OrganizationImpl organizationToRemove = manager .createQuery( "SELECT o FROM Organization o " + "WHERE o.id = :id", OrganizationImpl.class) .setParameter("id", createdOrganizations.get(i).getId()) .getSingleResult(); manager.remove(organizationToRemove); } catch (NoResultException ignored) { // it is already removed } } createdOrganizations.clear(); manager.getTransaction().commit(); } catch (RuntimeException x) { if (manager.getTransaction().isActive()) { manager.getTransaction().rollback(); } throw new TckRepositoryException(x.getLocalizedMessage(), x); } finally { uow.end(); } // remove all objects that was created in tests super.removeAll(); } } }