/*******************************************************************************
* Copyright (c) 2012-2017 Codenvy, S.A.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* Codenvy, S.A. - initial API and implementation
*******************************************************************************/
import com.google.inject.TypeLiteral;
import com.google.inject.persist.Transactional;
import org.eclipse.che.account.spi.AccountDao;
import org.eclipse.che.account.spi.AccountImpl;
import org.eclipse.che.account.spi.jpa.JpaAccountDao;
import org.eclipse.che.api.core.model.workspace.Workspace;
import org.eclipse.che.api.machine.server.jpa.JpaRecipeDao;
import org.eclipse.che.api.machine.server.jpa.JpaSnapshotDao;
import org.eclipse.che.api.machine.server.model.impl.CommandImpl;
import org.eclipse.che.api.machine.server.model.impl.SnapshotImpl;
import org.eclipse.che.api.machine.server.recipe.RecipeImpl;
import org.eclipse.che.api.machine.server.spi.RecipeDao;
import org.eclipse.che.api.machine.server.spi.SnapshotDao;
import org.eclipse.che.api.ssh.server.jpa.JpaSshDao;
import org.eclipse.che.api.ssh.server.model.impl.SshPairImpl;
import org.eclipse.che.api.ssh.server.spi.SshDao;
import org.eclipse.che.api.user.server.jpa.JpaPreferenceDao;
import org.eclipse.che.api.user.server.jpa.JpaProfileDao;
import org.eclipse.che.api.user.server.jpa.JpaUserDao;
import org.eclipse.che.api.user.server.jpa.PreferenceEntity;
import org.eclipse.che.api.user.server.model.impl.ProfileImpl;
import org.eclipse.che.api.user.server.model.impl.UserImpl;
import org.eclipse.che.api.user.server.spi.PreferenceDao;
import org.eclipse.che.api.user.server.spi.ProfileDao;
import org.eclipse.che.api.user.server.spi.UserDao;
import org.eclipse.che.api.workspace.server.jpa.JpaStackDao;
import org.eclipse.che.api.workspace.server.jpa.JpaWorkspaceDao;
import org.eclipse.che.api.workspace.server.model.impl.EnvironmentImpl;
import org.eclipse.che.api.workspace.server.model.impl.EnvironmentRecipeImpl;
import org.eclipse.che.api.workspace.server.model.impl.ExtendedMachineImpl;
import org.eclipse.che.api.workspace.server.model.impl.ProjectConfigImpl;
import org.eclipse.che.api.workspace.server.model.impl.ServerConf2Impl;
import org.eclipse.che.api.workspace.server.model.impl.SourceStorageImpl;
import org.eclipse.che.api.workspace.server.model.impl.WorkspaceConfigImpl;
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.api.workspace.server.spi.StackDao;
import org.eclipse.che.api.workspace.server.spi.WorkspaceDao;
import org.eclipse.che.commons.lang.Pair;
import org.eclipse.che.commons.test.db.PersistTestModuleBuilder;
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.postgresql.jpa.eclipselink.PostgreSqlExceptionHandler;
import org.eclipse.che.core.db.schema.SchemaInitializer;
import org.eclipse.che.core.db.schema.impl.flyway.FlywaySchemaInitializer;
import org.eclipse.che.security.PasswordEncryptor;
import org.eclipse.che.security.SHA512PasswordEncryptor;
import org.postgresql.Driver;
import org.postgresql.ds.PGSimpleDataSource;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.inject.Inject;
import javax.inject.Provider;
import javax.persistence.EntityManager;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.SQLException;
import java.util.Collection;
import java.util.Map;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;
/**
* Module for running TCKs based on PostgreSQL.
*
* @author Yevhenii Voevodin
*/
public class PostgreSqlTckModule extends TckModule {
private static final Logger LOG = LoggerFactory.getLogger(PostgreSqlTckModule.class);
@Override
protected void configure() {
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);
// jpa
install(new PersistTestModuleBuilder().setDriver(Driver.class)
.setUrl(dbUrl)
.setUser(dbUser)
.setPassword(dbPassword)
.setExceptionHandler(PostgreSqlExceptionHandler.class)
.addEntityClasses(AccountImpl.class,
UserImpl.class,
ProfileImpl.class,
PreferenceEntity.class,
WorkspaceImpl.class,
WorkspaceConfigImpl.class,
ProjectConfigImpl.class,
EnvironmentImpl.class,
EnvironmentRecipeImpl.class,
ExtendedMachineImpl.class,
SourceStorageImpl.class,
ServerConf2Impl.class,
StackImpl.class,
CommandImpl.class,
SnapshotImpl.class,
RecipeImpl.class,
SshPairImpl.class)
.addEntityClass("org.eclipse.che.api.workspace.server.model.impl.ProjectConfigImpl$Attribute")
.build());
bind(TckResourcesCleaner.class).to(JpaCleaner.class);
// db initialization
bind(DBInitializer.class).asEagerSingleton();
final PGSimpleDataSource dataSource = new PGSimpleDataSource();
dataSource.setUser(dbUser);
dataSource.setPassword(dbPassword);
dataSource.setUrl(dbUrl);
bind(SchemaInitializer.class).toInstance(new FlywaySchemaInitializer(dataSource, "che-schema"));
// account
bind(AccountDao.class).to(JpaAccountDao.class);
bind(new TypeLiteral<TckRepository<AccountImpl>>() {}).toInstance(new JpaTckRepository<>(AccountImpl.class));
// user
bind(UserDao.class).to(JpaUserDao.class);
bind(ProfileDao.class).to(JpaProfileDao.class);
bind(PreferenceDao.class).to(JpaPreferenceDao.class);
bind(new TypeLiteral<TckRepository<UserImpl>>() {}).to(UserRepo.class);
bind(new TypeLiteral<TckRepository<Pair<String, Map<String, String>>>>() {}).to(PreferencesRepo.class);
bind(new TypeLiteral<TckRepository<ProfileImpl>>() {}).toInstance(new JpaTckRepository<>(ProfileImpl.class));
bind(PasswordEncryptor.class).to(SHA512PasswordEncryptor.class);
// machine
bind(RecipeDao.class).to(JpaRecipeDao.class);
bind(SnapshotDao.class).to(JpaSnapshotDao.class);
bind(new TypeLiteral<TckRepository<RecipeImpl>>() {}).toInstance(new JpaTckRepository<>(RecipeImpl.class));
bind(new TypeLiteral<TckRepository<SnapshotImpl>>() {}).toInstance(new JpaTckRepository<>(SnapshotImpl.class));
bind(new TypeLiteral<TckRepository<Workspace>>() {}).toInstance(new WorkspaceRepoForSnapshots());
// ssh
bind(SshDao.class).to(JpaSshDao.class);
bind(new TypeLiteral<TckRepository<SshPairImpl>>() {}).toInstance(new JpaTckRepository<>(SshPairImpl.class));
// workspace
bind(WorkspaceDao.class).to(JpaWorkspaceDao.class);
bind(StackDao.class).to(JpaStackDao.class);
bind(new TypeLiteral<TckRepository<WorkspaceImpl>>() {}).toInstance(new WorkspaceRepository());
bind(new TypeLiteral<TckRepository<StackImpl>>() {}).toInstance(new StackRepository());
}
private static void waitConnectionIsEstablished(String dbUrl, String dbUser, String dbPassword) {
boolean isAvailable = false;
for (int i = 0; i < 60 && !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
static class PreferencesRepo implements TckRepository<Pair<String, Map<String, String>>> {
@Inject
private Provider<EntityManager> managerProvider;
@Override
public void createAll(Collection<? extends Pair<String, Map<String, String>>> entities)
throws TckRepositoryException {
final EntityManager manager = managerProvider.get();
for (Pair<String, Map<String, String>> pair : entities) {
manager.persist(new PreferenceEntity(pair.first, pair.second));
}
}
@Override
public void removeAll() throws TckRepositoryException {
final EntityManager manager = managerProvider.get();
manager.createQuery("SELECT preferences FROM Preference preferences", PreferenceEntity.class)
.getResultList()
.forEach(manager::remove);
}
}
@Transactional
static class UserRepo implements TckRepository<UserImpl> {
@Inject
private Provider<EntityManager> managerProvider;
@Inject
private PasswordEncryptor encryptor;
@Override
public void createAll(Collection<? extends UserImpl> entities) throws TckRepositoryException {
final EntityManager manager = managerProvider.get();
entities.stream()
.map(user -> new UserImpl(user.getId(),
user.getEmail(),
user.getName(),
encryptor.encrypt(user.getPassword()),
user.getAliases()))
.forEach(manager::persist);
}
@Override
public void removeAll() throws TckRepositoryException {
managerProvider.get()
.createQuery("SELECT u FROM Usr u", UserImpl.class)
.getResultList()
.forEach(managerProvider.get()::remove);
}
}
static class WorkspaceRepoForSnapshots extends JpaTckRepository<Workspace> {
public WorkspaceRepoForSnapshots() { super(WorkspaceImpl.class); }
@Override
public void createAll(Collection<? extends Workspace> entities) throws TckRepositoryException {
super.createAll(entities.stream()
.map(w -> new WorkspaceImpl(w, new AccountImpl(w.getNamespace(),
w.getNamespace(),
"simple")))
.collect(Collectors.toList()));
}
}
private static class WorkspaceRepository extends JpaTckRepository<WorkspaceImpl> {
public WorkspaceRepository() { super(WorkspaceImpl.class); }
@Override
public void createAll(Collection<? extends WorkspaceImpl> entities) throws TckRepositoryException {
for (WorkspaceImpl entity : entities) {
entity.getConfig().getProjects().forEach(ProjectConfigImpl::prePersistAttributes);
}
super.createAll(entities);
}
}
private static class StackRepository extends JpaTckRepository<StackImpl> {
public StackRepository() { super(StackImpl.class); }
@Override
public void createAll(Collection<? extends StackImpl> entities) throws TckRepositoryException {
for (StackImpl stack : entities) {
stack.getWorkspaceConfig().getProjects().forEach(ProjectConfigImpl::prePersistAttributes);
}
super.createAll(entities);
}
}
}