diff --git a/assembly/assembly-main/src/assembly/bin/che.sh b/assembly/assembly-main/src/assembly/bin/che.sh index 773aabc158..39a111e90e 100644 --- a/assembly/assembly-main/src/assembly/bin/che.sh +++ b/assembly/assembly-main/src/assembly/bin/che.sh @@ -608,7 +608,7 @@ call_catalina () { ### Cannot add this in setenv.sh. ### We do the port mapping here, and this gets inserted into server.xml when tomcat boots - export JAVA_OPTS="${JAVA_OPTS} -Dport.http=${CHE_PORT} -Dche.home=${CHE_HOME}" + export JAVA_OPTS="${JAVA_OPTS} -Dport.http=${CHE_PORT} -Dche.home=${CHE_HOME} -Dh2.baseDir=${CHE_HOME}/db/" export SERVER_PORT=${CHE_PORT} # Launch the Che application server, passing in command line parameters diff --git a/assembly/assembly-main/src/assembly/tomcat/conf/server.xml b/assembly/assembly-main/src/assembly/tomcat/conf/server.xml index 979497a52a..c584fd35ec 100644 --- a/assembly/assembly-main/src/assembly/tomcat/conf/server.xml +++ b/assembly/assembly-main/src/assembly/tomcat/conf/server.xml @@ -46,6 +46,14 @@ description="User database that can be updated and saved" factory="org.apache.catalina.users.MemoryUserDatabaseFactory" pathname="conf/tomcat-users.xml" /--> + + + + + org.eclipse.persistence.jpa.PersistenceProvider + java:/comp/env/jdbc/che + + org.eclipse.che.account.spi.AccountImpl + org.eclipse.che.api.user.server.model.impl.UserImpl + org.eclipse.che.api.user.server.model.impl.ProfileImpl + org.eclipse.che.api.user.server.jpa.PreferenceEntity + + org.eclipse.che.api.workspace.server.model.impl.WorkspaceImpl + org.eclipse.che.api.workspace.server.model.impl.WorkspaceConfigImpl + org.eclipse.che.api.workspace.server.model.impl.ProjectConfigImpl + org.eclipse.che.api.workspace.server.model.impl.EnvironmentImpl + org.eclipse.che.api.workspace.server.model.impl.EnvironmentRecipeImpl + org.eclipse.che.api.workspace.server.model.impl.ExtendedMachineImpl + org.eclipse.che.api.workspace.server.model.impl.ProjectConfigImpl$Attribute + org.eclipse.che.api.workspace.server.model.impl.SourceStorageImpl + org.eclipse.che.api.workspace.server.model.impl.ServerConf2Impl + org.eclipse.che.api.workspace.server.model.impl.stack.StackImpl + + org.eclipse.che.api.machine.server.model.impl.CommandImpl + org.eclipse.che.api.machine.server.model.impl.MachineSourceImpl + org.eclipse.che.api.machine.server.model.impl.SnapshotImpl + org.eclipse.che.api.machine.server.recipe.RecipeImpl + + org.eclipse.che.api.ssh.server.model.impl.SshPairImpl + + true + + + + + + + + + + diff --git a/assembly/assembly-wsmaster-war/src/main/webapp/META-INF/context.xml b/assembly/assembly-wsmaster-war/src/main/webapp/META-INF/context.xml index 5120e3e23c..f02252d815 100644 --- a/assembly/assembly-wsmaster-war/src/main/webapp/META-INF/context.xml +++ b/assembly/assembly-wsmaster-war/src/main/webapp/META-INF/context.xml @@ -13,4 +13,8 @@ --> - \ No newline at end of file + + + diff --git a/assembly/assembly-wsmaster-war/src/main/webapp/WEB-INF/classes/codenvy/che.properties b/assembly/assembly-wsmaster-war/src/main/webapp/WEB-INF/classes/codenvy/che.properties index 46d5a24e18..399fbdc4cf 100644 --- a/assembly/assembly-wsmaster-war/src/main/webapp/WEB-INF/classes/codenvy/che.properties +++ b/assembly/assembly-wsmaster-war/src/main/webapp/WEB-INF/classes/codenvy/che.properties @@ -172,7 +172,7 @@ workspace.runtime.auto_snapshot=true workspace.runtime.auto_restore=true # Reserved user names -user.reserved_names= +che.account.reserved_names= # java opts for dev machine che.machine.java_opts=-Xms256m -Xmx2048m -Djava.security.egd=file:/dev/./urandom diff --git a/assembly/assembly-wsmaster-war/src/main/webapp/WEB-INF/web.xml b/assembly/assembly-wsmaster-war/src/main/webapp/WEB-INF/web.xml index 495678193b..804ac84a7f 100644 --- a/assembly/assembly-wsmaster-war/src/main/webapp/WEB-INF/web.xml +++ b/assembly/assembly-wsmaster-war/src/main/webapp/WEB-INF/web.xml @@ -68,4 +68,10 @@ developer + + jdbc/che + javax.sql.DataSource + Container + + diff --git a/assembly/assembly-wsmaster-war/src/test/java/org/eclipse/che/api/TestObjectsFactory.java b/assembly/assembly-wsmaster-war/src/test/java/org/eclipse/che/api/TestObjectsFactory.java new file mode 100644 index 0000000000..b743647e27 --- /dev/null +++ b/assembly/assembly-wsmaster-war/src/test/java/org/eclipse/che/api/TestObjectsFactory.java @@ -0,0 +1,133 @@ +/******************************************************************************* + * Copyright (c) 2012-2016 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 + *******************************************************************************/ +package org.eclipse.che.api; + +import com.google.common.collect.ImmutableMap; + +import org.eclipse.che.account.shared.model.Account; +import org.eclipse.che.api.core.model.workspace.WorkspaceConfig; +import org.eclipse.che.api.factory.server.model.impl.AuthorImpl; +import org.eclipse.che.api.factory.server.model.impl.FactoryImpl; +import org.eclipse.che.api.machine.server.model.impl.SnapshotImpl; +import org.eclipse.che.api.machine.server.recipe.RecipeImpl; +import org.eclipse.che.api.ssh.server.model.impl.SshPairImpl; +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.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.StackComponentImpl; +import org.eclipse.che.api.workspace.server.model.impl.stack.StackImpl; +import org.eclipse.che.api.workspace.server.model.impl.stack.StackSourceImpl; +import org.eclipse.che.api.workspace.server.stack.image.StackIcon; + +import java.util.HashMap; +import java.util.Map; + +import static java.util.Arrays.asList; + +/** + * Defines method for creating tests object instances. + * + * @author Yevhenii Voevodin + */ +public final class TestObjectsFactory { + + public static UserImpl createUser(String id) { + return new UserImpl(id, + id + "@eclipse.org", + id + "_name", + "password", + asList(id + "_alias1", id + "_alias2")); + } + + public static ProfileImpl createProfile(String userId) { + return new ProfileImpl(userId, new HashMap<>(ImmutableMap.of("attribute1", "value1", + "attribute2", "value2", + "attribute3", "value3"))); + } + + public static Map createPreferences() { + return new HashMap<>(ImmutableMap.of("preference1", "value1", + "preference2", "value2", + "preference3", "value3")); + } + + public static WorkspaceConfigImpl createWorkspaceConfig(String id) { + return new WorkspaceConfigImpl(id + "_name", + id + "description", + "default-env", + null, + null, + null); + } + + public static WorkspaceImpl createWorkspace(String id, Account account) { + return new WorkspaceImpl(id, account, createWorkspaceConfig(id)); + } + + public static SshPairImpl createSshPair(String owner, String service, String name) { + return new SshPairImpl(owner, service, name, "public-key", "private-key"); + } + + public static FactoryImpl createFactory(String id, String creator) { + return new FactoryImpl(id, + id + "-name", + "4.0", + createWorkspaceConfig(id), + new AuthorImpl(creator, System.currentTimeMillis()), + null, + null, + null, + null); + } + + public static SnapshotImpl createSnapshot(String snapshotId, String workspaceId) { + return new SnapshotImpl(snapshotId, + "type", + null, + System.currentTimeMillis(), + workspaceId, + snapshotId + "_description", + true, + "dev-machine", + snapshotId + "env-name"); + } + + public static RecipeImpl createRecipe(String id) { + return new RecipeImpl(id, + "recipe-name-" + id, + "recipe-creator", + "recipe-type", + "recipe-script", + asList("recipe-tag1", "recipe-tag2"), + "recipe-description"); + } + + public static StackImpl createStack(String id, String name) { + return StackImpl.builder() + .setId(id) + .setName(name) + .setCreator("user123") + .setDescription(id + "-description") + .setScope(id + "-scope") + .setWorkspaceConfig(createWorkspaceConfig("test")) + .setTags(asList(id + "-tag1", id + "-tag2")) + .setComponents(asList(new StackComponentImpl(id + "-component1", id + "-component1-version"), + new StackComponentImpl(id + "-component2", id + "-component2-version"))) + .setSource(new StackSourceImpl(id + "-type", id + "-origin")) + .setStackIcon(new StackIcon(id + "-icon", + id + "-media-type", + "0x1234567890abcdef".getBytes())) + .build(); + } + + private TestObjectsFactory() {} +} diff --git a/assembly/assembly-wsmaster-war/src/test/java/org/eclipse/che/api/jdbc/jpa/JpaEntitiesCascadeRemovalTest.java b/assembly/assembly-wsmaster-war/src/test/java/org/eclipse/che/api/jdbc/jpa/JpaEntitiesCascadeRemovalTest.java new file mode 100644 index 0000000000..633ebd6f88 --- /dev/null +++ b/assembly/assembly-wsmaster-war/src/test/java/org/eclipse/che/api/jdbc/jpa/JpaEntitiesCascadeRemovalTest.java @@ -0,0 +1,261 @@ +/******************************************************************************* + * Copyright (c) 2012-2016 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 + *******************************************************************************/ +package org.eclipse.che.api.jdbc.jpa; + +import com.google.inject.AbstractModule; +import com.google.inject.Guice; +import com.google.inject.Injector; +import com.google.inject.Stage; +import com.google.inject.persist.jpa.JpaPersistModule; + +import org.eclipse.che.api.core.ConflictException; +import org.eclipse.che.api.core.NotFoundException; +import org.eclipse.che.api.core.ServerException; +import org.eclipse.che.api.core.jdbc.jpa.eclipselink.EntityListenerInjectionManagerInitializer; +import org.eclipse.che.api.core.jdbc.jpa.guice.JpaInitializer; +import org.eclipse.che.api.core.notification.EventService; +import org.eclipse.che.api.core.notification.EventSubscriber; +import org.eclipse.che.api.factory.server.jpa.FactoryJpaModule; +import org.eclipse.che.api.factory.server.jpa.JpaFactoryDao.RemoveFactoriesBeforeUserRemovedEventSubscriber; +import org.eclipse.che.api.factory.server.model.impl.FactoryImpl; +import org.eclipse.che.api.factory.server.spi.FactoryDao; +import org.eclipse.che.api.machine.server.jpa.MachineJpaModule; +import org.eclipse.che.api.machine.server.model.impl.SnapshotImpl; +import org.eclipse.che.api.machine.server.spi.SnapshotDao; +import org.eclipse.che.api.ssh.server.jpa.JpaSshDao.RemoveSshKeysBeforeUserRemovedEventSubscriber; +import org.eclipse.che.api.ssh.server.jpa.SshJpaModule; +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.RemovePreferencesBeforeUserRemovedEventSubscriber; +import org.eclipse.che.api.user.server.jpa.JpaProfileDao.RemoveProfileBeforeUserRemovedEventSubscriber; +import org.eclipse.che.api.user.server.jpa.UserJpaModule; +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.JpaWorkspaceDao; +import org.eclipse.che.api.workspace.server.jpa.JpaWorkspaceDao.RemoveSnapshotsBeforeWorkspaceRemovedEventSubscriber; +import org.eclipse.che.api.workspace.server.jpa.JpaWorkspaceDao.RemoveWorkspaceBeforeAccountRemovedEventSubscriber; +import org.eclipse.che.api.workspace.server.jpa.WorkspaceJpaModule; +import org.eclipse.che.api.workspace.server.model.impl.WorkspaceImpl; +import org.eclipse.che.api.workspace.server.spi.WorkspaceDao; +import org.eclipse.che.commons.lang.Pair; +import org.eclipse.che.inject.lifecycle.InitModule; +import org.testng.annotations.AfterMethod; +import org.testng.annotations.BeforeMethod; +import org.testng.annotations.DataProvider; +import org.testng.annotations.Test; + +import javax.annotation.PostConstruct; +import javax.inject.Singleton; +import javax.persistence.EntityManagerFactory; +import java.util.Map; +import java.util.concurrent.Callable; + +import static java.util.Collections.singletonList; +import static org.eclipse.che.api.TestObjectsFactory.createFactory; +import static org.eclipse.che.api.TestObjectsFactory.createPreferences; +import static org.eclipse.che.api.TestObjectsFactory.createProfile; +import static org.eclipse.che.api.TestObjectsFactory.createSnapshot; +import static org.eclipse.che.api.TestObjectsFactory.createSshPair; +import static org.eclipse.che.api.TestObjectsFactory.createUser; +import static org.eclipse.che.api.TestObjectsFactory.createWorkspace; +import static org.testng.Assert.assertFalse; +import static org.testng.Assert.assertNotNull; +import static org.testng.Assert.assertNull; +import static org.testng.Assert.assertTrue; +import static org.testng.Assert.fail; + +/** + * Tests top-level entities cascade removals. + * + * @author Yevhenii Voevodin + */ +public class JpaEntitiesCascadeRemovalTest { + + private Injector injector; + private EventService eventService; + private PreferenceDao preferenceDao; + private UserDao userDao; + private ProfileDao profileDao; + private WorkspaceDao workspaceDao; + private SnapshotDao snapshotDao; + private SshDao sshDao; + private FactoryDao factoryDao; + + /** User is a root of dependency tree. */ + private UserImpl user; + + /** Profile depends on user. */ + private ProfileImpl profile; + + /** Preferences depend on user. */ + private Map preferences; + + /** Workspaces depend on user. */ + private WorkspaceImpl workspace1; + private WorkspaceImpl workspace2; + + /** SshPairs depend on user. */ + private SshPairImpl sshPair1; + private SshPairImpl sshPair2; + + /** Factories depend on user. */ + private FactoryImpl factory1; + private FactoryImpl factory2; + + /** Snapshots depend on workspace. */ + private SnapshotImpl snapshot1; + private SnapshotImpl snapshot2; + private SnapshotImpl snapshot3; + private SnapshotImpl snapshot4; + + @BeforeMethod + public void setUp() throws Exception { + injector = Guice.createInjector(Stage.PRODUCTION, new AbstractModule() { + @Override + protected void configure() { + bind(EventService.class).in(Singleton.class); + + bind(JpaInitializer.class).asEagerSingleton(); + bind(EntityListenerInjectionManagerInitializer.class).asEagerSingleton(); + install(new InitModule(PostConstruct.class)); + install(new JpaPersistModule("test")); + install(new UserJpaModule()); + install(new SshJpaModule()); + install(new WorkspaceJpaModule()); + install(new MachineJpaModule()); + install(new FactoryJpaModule()); + } + }); + + eventService = injector.getInstance(EventService.class); + userDao = injector.getInstance(UserDao.class); + preferenceDao = injector.getInstance(PreferenceDao.class); + profileDao = injector.getInstance(ProfileDao.class); + sshDao = injector.getInstance(SshDao.class); + snapshotDao = injector.getInstance(SnapshotDao.class); + workspaceDao = injector.getInstance(WorkspaceDao.class); + factoryDao = injector.getInstance(FactoryDao.class); + } + + @AfterMethod + public void cleanup() { + injector.getInstance(EntityManagerFactory.class).close(); + } + + @Test + public void shouldDeleteAllTheEntitiesWhenUserIsDeleted() throws Exception { + createTestData(); + + // Remove the user, all entries must be removed along with the user + userDao.remove(user.getId()); + + // Check all the entities are removed + assertNull(notFoundToNull(() -> userDao.getById(user.getId()))); + assertNull(notFoundToNull(() -> profileDao.getById(user.getId()))); + assertTrue(preferenceDao.getPreferences(user.getId()).isEmpty()); + assertTrue(sshDao.get(user.getId()).isEmpty()); + assertTrue(workspaceDao.getByNamespace(user.getId()).isEmpty()); + assertTrue(factoryDao.getByAttribute(0, 0, singletonList(Pair.of("creator.userId", user.getId()))).isEmpty()); + assertTrue(snapshotDao.findSnapshots(workspace1.getId()).isEmpty()); + assertTrue(snapshotDao.findSnapshots(workspace2.getId()).isEmpty()); + } + + @Test(dataProvider = "beforeRemoveRollbackActions") + public void shouldRollbackTransactionWhenFailedToRemoveAnyOfEntries(Class eventSubscriber) throws Exception { + createTestData(); + eventService.unsubscribe(injector.getInstance(eventSubscriber)); + + // Remove the user, all entries must be rolled back after fail + try { + userDao.remove(user.getId()); + fail("UserDao#remove had to throw exception"); + } catch (Exception ignored) { + } + + // Check all the data rolled back + assertNotNull(userDao.getById(user.getId())); + assertNotNull(profileDao.getById(user.getId())); + assertFalse(preferenceDao.getPreferences(user.getId()).isEmpty()); + assertFalse(sshDao.get(user.getId()).isEmpty()); + assertFalse(workspaceDao.getByNamespace(user.getName()).isEmpty()); + assertFalse(factoryDao.getByAttribute(0, 0, singletonList(Pair.of("creator.userId", user.getId()))).isEmpty()); + assertFalse(snapshotDao.findSnapshots(workspace1.getId()).isEmpty()); + assertFalse(snapshotDao.findSnapshots(workspace2.getId()).isEmpty()); + wipeTestData(); + } + + @DataProvider(name = "beforeRemoveRollbackActions") + public Object[][] beforeRemoveActions() { + return new Class[][] { + {RemovePreferencesBeforeUserRemovedEventSubscriber.class}, + {RemoveProfileBeforeUserRemovedEventSubscriber.class}, + {RemoveWorkspaceBeforeAccountRemovedEventSubscriber.class}, + {RemoveSnapshotsBeforeWorkspaceRemovedEventSubscriber.class}, + {RemoveSshKeysBeforeUserRemovedEventSubscriber.class}, + {RemoveFactoriesBeforeUserRemovedEventSubscriber.class} + }; + } + + private void createTestData() throws ConflictException, ServerException { + userDao.create(user = createUser("bobby")); + + profileDao.create(profile = createProfile(user.getId())); + + preferenceDao.setPreferences(user.getId(), preferences = createPreferences()); + + workspaceDao.create(workspace1 = createWorkspace("workspace1", user.getAccount())); + workspaceDao.create(workspace2 = createWorkspace("workspace2", user.getAccount())); + + sshDao.create(sshPair1 = createSshPair(user.getId(), "service", "name1")); + sshDao.create(sshPair2 = createSshPair(user.getId(), "service", "name2")); + + factoryDao.create(factory1 = createFactory("factory1", user.getId())); + factoryDao.create(factory2 = createFactory("factory2", user.getId())); + + snapshotDao.saveSnapshot(snapshot1 = createSnapshot("snapshot1", workspace1.getId())); + snapshotDao.saveSnapshot(snapshot2 = createSnapshot("snapshot2", workspace1.getId())); + snapshotDao.saveSnapshot(snapshot3 = createSnapshot("snapshot3", workspace2.getId())); + snapshotDao.saveSnapshot(snapshot4 = createSnapshot("snapshot4", workspace2.getId())); + } + + private void wipeTestData() throws ConflictException, ServerException, NotFoundException { + snapshotDao.removeSnapshot(snapshot1.getId()); + snapshotDao.removeSnapshot(snapshot2.getId()); + snapshotDao.removeSnapshot(snapshot3.getId()); + snapshotDao.removeSnapshot(snapshot4.getId()); + + factoryDao.remove(factory1.getId()); + factoryDao.remove(factory2.getId()); + + sshDao.remove(sshPair1.getOwner(), sshPair1.getService(), sshPair1.getName()); + sshDao.remove(sshPair2.getOwner(), sshPair2.getService(), sshPair2.getName()); + + workspaceDao.remove(workspace1.getId()); + workspaceDao.remove(workspace2.getId()); + + preferenceDao.remove(user.getId()); + + profileDao.remove(user.getId()); + + userDao.remove(user.getId()); + } + + private static T notFoundToNull(Callable action) throws Exception { + try { + return action.call(); + } catch (NotFoundException x) { + return null; + } + } +} diff --git a/assembly/assembly-wsmaster-war/src/test/java/org/eclipse/che/api/local/LocalToJpaDataMigratorTest.java b/assembly/assembly-wsmaster-war/src/test/java/org/eclipse/che/api/local/LocalToJpaDataMigratorTest.java new file mode 100644 index 0000000000..22df1321dd --- /dev/null +++ b/assembly/assembly-wsmaster-war/src/test/java/org/eclipse/che/api/local/LocalToJpaDataMigratorTest.java @@ -0,0 +1,222 @@ +/******************************************************************************* + * Copyright (c) 2012-2016 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 + *******************************************************************************/ +package org.eclipse.che.api.local; + +import com.google.gson.Gson; +import com.google.gson.JsonElement; +import com.google.gson.JsonObject; +import com.google.gson.JsonPrimitive; +import com.google.gson.JsonSerializationContext; +import com.google.gson.JsonSerializer; +import com.google.inject.AbstractModule; +import com.google.inject.Guice; +import com.google.inject.Injector; +import com.google.inject.Stage; +import com.google.inject.name.Names; +import com.google.inject.persist.jpa.JpaPersistModule; + +import org.eclipse.che.api.core.jdbc.jpa.guice.JpaInitializer; +import org.eclipse.che.api.local.storage.LocalStorageFactory; +import org.eclipse.che.api.local.storage.stack.StackLocalStorage; +import org.eclipse.che.api.machine.server.jpa.MachineJpaModule; +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.SshJpaModule; +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.UserJpaModule; +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.WorkspaceConfigJsonAdapter; +import org.eclipse.che.api.workspace.server.jpa.WorkspaceJpaModule; +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.api.workspace.server.stack.StackJsonAdapter; +import org.eclipse.che.commons.lang.IoUtil; +import org.testng.annotations.AfterMethod; +import org.testng.annotations.BeforeMethod; +import org.testng.annotations.DataProvider; +import org.testng.annotations.Test; + +import javax.persistence.EntityManagerFactory; +import java.lang.reflect.Type; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.Map; + +import static java.util.Collections.singletonList; +import static java.util.Collections.singletonMap; +import static org.eclipse.che.api.TestObjectsFactory.createPreferences; +import static org.eclipse.che.api.TestObjectsFactory.createProfile; +import static org.eclipse.che.api.TestObjectsFactory.createRecipe; +import static org.eclipse.che.api.TestObjectsFactory.createSnapshot; +import static org.eclipse.che.api.TestObjectsFactory.createSshPair; +import static org.eclipse.che.api.TestObjectsFactory.createStack; +import static org.eclipse.che.api.TestObjectsFactory.createUser; +import static org.eclipse.che.api.TestObjectsFactory.createWorkspace; + +/** + * Tests migration from local json based storage to jpa. + * + * @author Yevhenii Voevodin + */ +public class LocalToJpaDataMigratorTest { + + private Injector injector; + private LocalDataMigrator migrator; + private Path workingDir; + + private UserDao userDao; + private ProfileDao profileDao; + private PreferenceDao preferenceDao; + private WorkspaceDao workspaceDao; + private SnapshotDao snapshotDao; + private SshDao sshDao; + private RecipeDao recipeDao; + private StackDao stackDao; + + private LocalStorageFactory factory; + private StackJsonAdapter stackJsonAdapter; + private WorkspaceConfigJsonAdapter workspaceCfgJsonAdapter; + + @BeforeMethod + private void setUp() throws Exception { + workingDir = Files.createTempDirectory(Paths.get("/tmp"), "test"); + factory = new LocalStorageFactory(workingDir.toString()); + injector = Guice.createInjector(Stage.PRODUCTION, new AbstractModule() { + @Override + protected void configure() { + bindConstant().annotatedWith(Names.named("che.conf.storage")).to(workingDir.toString()); + + bind(JpaInitializer.class).asEagerSingleton(); + install(new JpaPersistModule("test")); + install(new UserJpaModule()); + install(new SshJpaModule()); + install(new WorkspaceJpaModule()); + install(new MachineJpaModule()); + bind(StackJsonAdapter.class); + } + }); + + userDao = injector.getInstance(UserDao.class); + preferenceDao = injector.getInstance(PreferenceDao.class); + profileDao = injector.getInstance(ProfileDao.class); + sshDao = injector.getInstance(SshDao.class); + snapshotDao = injector.getInstance(SnapshotDao.class); + workspaceDao = injector.getInstance(WorkspaceDao.class); + recipeDao = injector.getInstance(RecipeDao.class); + stackDao = injector.getInstance(StackDao.class); + + stackJsonAdapter = injector.getInstance(StackJsonAdapter.class); + workspaceCfgJsonAdapter = injector.getInstance(WorkspaceConfigJsonAdapter.class); + + migrator = new LocalDataMigrator(); + storeTestData(); + } + + @AfterMethod + public void cleanup() { + IoUtil.deleteRecursive(workingDir.toFile()); + injector.getInstance(EntityManagerFactory.class).close(); + } + + @Test + public void shouldSuccessfullyPerformMigration() throws Exception { + migrator.performMigration(workingDir.toString(), + userDao, + profileDao, + preferenceDao, + sshDao, + workspaceDao, + snapshotDao, + recipeDao, + stackDao, + stackJsonAdapter, + workspaceCfgJsonAdapter); + } + + @Test(expectedExceptions = Exception.class, dataProvider = "failFilenames") + public void shouldFailIfEntitiesAreInconsistent(String filename) throws Exception { + Files.delete(workingDir.resolve(filename)); + migrator.performMigration(workingDir.toString(), + userDao, + profileDao, + preferenceDao, + sshDao, + workspaceDao, + snapshotDao, + recipeDao, + stackDao, + stackJsonAdapter, + workspaceCfgJsonAdapter); + } + + @DataProvider(name = "failFilenames") + private Object[][] failFilenames() { + return new String[][] { + {LocalUserDaoImpl.FILENAME}, + {LocalWorkspaceDaoImpl.FILENAME} + }; + } + + private void storeTestData() throws Exception { + final UserImpl user = createUser("user123"); + final ProfileImpl profile = createProfile(user.getId()); + final Map preferences = createPreferences(); + final SshPairImpl pair = createSshPair(user.getId(), "service", "name"); + final WorkspaceImpl workspace = createWorkspace("id", user.getAccount()); + final SnapshotImpl snapshot = createSnapshot("snapshot123", workspace.getId()); + final RecipeImpl recipe = createRecipe("recipe123"); + final StackImpl stack = createStack("stack123", "stack-name"); + + factory.create(LocalUserDaoImpl.FILENAME).store(singletonMap(user.getId(), user)); + factory.create(LocalProfileDaoImpl.FILENAME).store(singletonMap(profile.getUserId(), profile)); + factory.create(LocalPreferenceDaoImpl.FILENAME).store(singletonMap(user.getId(), preferences)); + factory.create(LocalSshDaoImpl.FILENAME, singletonMap(SshPairImpl.class, new SshSerializer())) + .store(singletonMap(pair.getOwner(), singletonList(pair))); + factory.create(LocalWorkspaceDaoImpl.FILENAME, singletonMap(WorkspaceImpl.class, new WorkspaceSerializer())) + .store(singletonMap(workspace.getId(), workspace)); + factory.create(LocalSnapshotDaoImpl.FILENAME).store(singletonMap(snapshot.getId(), snapshot)); + factory.create(LocalRecipeDaoImpl.FILENAME).store(singletonMap(recipe.getId(), recipe)); + factory.create(StackLocalStorage.STACK_STORAGE_FILE).store(singletonMap(stack.getId(), stack)); + } + + public static class WorkspaceSerializer implements JsonSerializer { + @Override + public JsonElement serialize(WorkspaceImpl src, Type typeOfSrc, JsonSerializationContext context) { + JsonElement result = new Gson().toJsonTree(src, WorkspaceImpl.class); + result.getAsJsonObject().addProperty("namespace", src.getNamespace()); + result.getAsJsonObject().remove("account"); + return result; + } + } + + public static class SshSerializer implements JsonSerializer { + + @Override + public JsonElement serialize(SshPairImpl sshPair, Type type, JsonSerializationContext jsonSerializationContext) { + JsonObject result = new JsonObject(); + result.add("service", new JsonPrimitive(sshPair.getService())); + result.add("name", new JsonPrimitive(sshPair.getName())); + result.add("privateKey", new JsonPrimitive(sshPair.getPublicKey())); + result.add("publicKey", new JsonPrimitive(sshPair.getPrivateKey())); + return result; + } + } +} diff --git a/assembly/assembly-wsmaster-war/src/test/resources/META-INF/persistence.xml b/assembly/assembly-wsmaster-war/src/test/resources/META-INF/persistence.xml new file mode 100644 index 0000000000..f4c4d6bcab --- /dev/null +++ b/assembly/assembly-wsmaster-war/src/test/resources/META-INF/persistence.xml @@ -0,0 +1,63 @@ + + + + org.eclipse.persistence.jpa.PersistenceProvider + org.eclipse.che.api.user.server.model.impl.UserImpl + org.eclipse.che.api.user.server.model.impl.ProfileImpl + org.eclipse.che.account.spi.AccountImpl + org.eclipse.che.api.user.server.jpa.PreferenceEntity + org.eclipse.che.api.workspace.server.model.impl.WorkspaceImpl + org.eclipse.che.api.workspace.server.model.impl.WorkspaceConfigImpl + org.eclipse.che.api.workspace.server.model.impl.ProjectConfigImpl + org.eclipse.che.api.workspace.server.model.impl.EnvironmentImpl + org.eclipse.che.api.workspace.server.model.impl.EnvironmentRecipeImpl + org.eclipse.che.api.workspace.server.model.impl.ExtendedMachineImpl + org.eclipse.che.api.workspace.server.model.impl.ProjectConfigImpl$Attribute + org.eclipse.che.api.workspace.server.model.impl.SourceStorageImpl + org.eclipse.che.api.workspace.server.model.impl.ServerConf2Impl + org.eclipse.che.api.workspace.server.model.impl.stack.StackImpl + org.eclipse.che.api.machine.server.model.impl.CommandImpl + org.eclipse.che.api.machine.server.model.impl.MachineSourceImpl + org.eclipse.che.api.machine.server.model.impl.SnapshotImpl + org.eclipse.che.api.machine.server.recipe.RecipeImpl + org.eclipse.che.api.ssh.server.model.impl.SshPairImpl + org.eclipse.che.api.factory.server.model.impl.ActionImpl + org.eclipse.che.api.factory.server.model.impl.AuthorImpl + org.eclipse.che.api.factory.server.model.impl.ButtonAttributesImpl + org.eclipse.che.api.factory.server.model.impl.ButtonImpl + org.eclipse.che.api.factory.server.model.impl.FactoryImpl + org.eclipse.che.api.factory.server.model.impl.IdeImpl + org.eclipse.che.api.factory.server.model.impl.OnAppClosedImpl + org.eclipse.che.api.factory.server.model.impl.OnProjectsLoadedImpl + org.eclipse.che.api.factory.server.model.impl.OnAppLoadedImpl + org.eclipse.che.api.factory.server.model.impl.PoliciesImpl + org.eclipse.che.api.factory.server.FactoryImage + true + + + + + + + + + + + + + + + diff --git a/assembly/assembly-wsmaster-war/src/test/resources/logback-test.xml b/assembly/assembly-wsmaster-war/src/test/resources/logback-test.xml new file mode 100644 index 0000000000..0d5e22c3c6 --- /dev/null +++ b/assembly/assembly-wsmaster-war/src/test/resources/logback-test.xml @@ -0,0 +1,25 @@ + + + + + + + %-41(%date[%.15thread]) %-45([%-5level] [%.30logger{30} %L]) - %msg%n + + + + + + + diff --git a/core/che-core-api-core/src/main/java/org/eclipse/che/api/core/acl/AclEntryImpl.java b/core/che-core-api-core/src/main/java/org/eclipse/che/api/core/acl/AclEntryImpl.java deleted file mode 100644 index 8738835375..0000000000 --- a/core/che-core-api-core/src/main/java/org/eclipse/che/api/core/acl/AclEntryImpl.java +++ /dev/null @@ -1,70 +0,0 @@ -/******************************************************************************* - * Copyright (c) 2012-2016 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 - *******************************************************************************/ -package org.eclipse.che.api.core.acl; - -import java.util.ArrayList; -import java.util.List; -import java.util.Objects; - -import static com.google.common.base.Preconditions.checkArgument; - -/** - * @author Sergii Leschenko - */ -public class AclEntryImpl implements AclEntry { - private final String user; - private final List actions; - - public AclEntryImpl(String user, List actions) { - checkArgument(actions != null && !actions.isEmpty(), "Required at least one action"); - this.user = user; - this.actions = new ArrayList<>(actions); - } - - @Override - public String getUser() { - return user; - } - - @Override - public List getActions() { - return actions; - } - - @Override - public boolean equals(Object obj) { - if (this == obj) { - return true; - } - if (!(obj instanceof AclEntryImpl)) { - return false; - } - final AclEntryImpl other = (AclEntryImpl)obj; - return Objects.equals(user, other.user) - && actions.equals(other.actions); - } - - @Override - public int hashCode() { - int hash = 7; - hash = 31 * hash + Objects.hashCode(user); - hash = 31 * hash + actions.hashCode(); - return hash; - } - - @Override - public String toString() { - return "AclEntryImpl{" + - "user='" + user + "'" + - ", actions=" + actions + - '}'; - } -} diff --git a/core/che-core-api-core/src/main/java/org/eclipse/che/security/PBKDF2PasswordEncryptor.java b/core/che-core-api-core/src/main/java/org/eclipse/che/security/PBKDF2PasswordEncryptor.java new file mode 100644 index 0000000000..944afa1a18 --- /dev/null +++ b/core/che-core-api-core/src/main/java/org/eclipse/che/security/PBKDF2PasswordEncryptor.java @@ -0,0 +1,87 @@ +/******************************************************************************* + * Copyright (c) 2012-2016 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 + *******************************************************************************/ +package org.eclipse.che.security; + +import com.google.common.hash.HashCode; + +import javax.crypto.SecretKeyFactory; +import javax.crypto.spec.PBEKeySpec; +import java.security.NoSuchAlgorithmException; +import java.security.SecureRandom; +import java.security.spec.InvalidKeySpecException; +import java.security.spec.KeySpec; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +import static com.google.common.base.MoreObjects.firstNonNull; +import static com.google.common.primitives.Ints.tryParse; +import static java.lang.String.format; +import static java.util.Objects.requireNonNull; + +/** + * Encrypts password using Password-based-Key-Derivative-Function + * with SHA512 as pseudorandom function. + * See rfc2898. + * + * @author Yevhenii Voevodin + */ +public class PBKDF2PasswordEncryptor implements PasswordEncryptor { + + private static final String PWD_FMT = "%s:%s:%d"; + private static final Pattern PWD_REGEX = Pattern.compile("(?\\w+):(?\\w+):(?[0-9]+)"); + + private static final String SECRET_KEY_FACTORY_NAME = "PBKDF2WithHmacSHA512"; + private static final SecureRandom SECURE_RANDOM = new SecureRandom(); + /** + * Minimum number of iterations required is 1_000(rfc2898), + * pick greater as potentially safer in the case of brute-force attacks . + */ + private static final int ITERATIONS_COUNT = 10_000; + /** 64bit salt length based on the rfc2898 spec . */ + private static final int SALT_LENGTH = 64 / 8; + + @Override + public String encrypt(String password) { + requireNonNull(password, "Required non-null password"); + final byte[] salt = new byte[SALT_LENGTH]; + SECURE_RANDOM.nextBytes(salt); + final HashCode hash = computeHash(password.toCharArray(), salt, ITERATIONS_COUNT); + final HashCode saltHash = HashCode.fromBytes(salt); + return format(PWD_FMT, hash, saltHash, ITERATIONS_COUNT); + } + + @Override + public boolean test(String password, String encryptedPassword) { + requireNonNull(password, "Required non-null password"); + requireNonNull(password, "Required non-null encrypted password"); + final Matcher matcher = PWD_REGEX.matcher(encryptedPassword); + if (!matcher.matches()) { + return false; + } + // retrieve salt, password hash and iterations count from hash + final Integer iterations = tryParse(matcher.group("iterations")); + final String salt = matcher.group("saltHash"); + final String pwdHash = matcher.group("pwdHash"); + // compute password's hash and test whether it matches to given hash + final HashCode hash = computeHash(password.toCharArray(), HashCode.fromString(salt).asBytes(), iterations); + return hash.toString().equals(pwdHash); + } + + private HashCode computeHash(char[] password, byte[] salt, int iterations) { + try { + final SecretKeyFactory keyFactory = SecretKeyFactory.getInstance(SECRET_KEY_FACTORY_NAME); + final KeySpec keySpec = new PBEKeySpec(password, salt, iterations, 512); + return HashCode.fromBytes(keyFactory.generateSecret(keySpec).getEncoded()); + } catch (NoSuchAlgorithmException | InvalidKeySpecException x) { + throw new RuntimeException(x.getMessage(), x); + } + } +} diff --git a/core/che-core-api-core/src/main/java/org/eclipse/che/security/PasswordEncryptor.java b/core/che-core-api-core/src/main/java/org/eclipse/che/security/PasswordEncryptor.java new file mode 100644 index 0000000000..fcc07c0c53 --- /dev/null +++ b/core/che-core-api-core/src/main/java/org/eclipse/che/security/PasswordEncryptor.java @@ -0,0 +1,47 @@ +/******************************************************************************* + * Copyright (c) 2012-2016 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 + *******************************************************************************/ +package org.eclipse.che.security; + +/** + * Encrypts password in implementation specific way. + * + * @author Yevhenii Voevodin + */ +public interface PasswordEncryptor { + + /** + * Encrypts the given {@code password}. + * + * @param password + * the plain password to be encrypted + * @return the encrypted password + * @throws NullPointerException + * when the password is null + * @throws RuntimeException + * when any error occurs during password encryption + */ + String encrypt(String password); + + /** + * Tests whether given {@code password} is {@code encryptedPassword}. + * + * @param encryptedPassword + * encrypted password + * @param password + * the password to check + * @return true if given {@code password} is {@code encryptedPassword} + * @throws NullPointerException + * when either of arguments is null + * @throws RuntimeException + * when any error occurs during test + */ + boolean test(String password, String encryptedPassword); +} diff --git a/core/che-core-api-core/src/main/java/org/eclipse/che/security/SHA512PasswordEncryptor.java b/core/che-core-api-core/src/main/java/org/eclipse/che/security/SHA512PasswordEncryptor.java new file mode 100644 index 0000000000..6efe7bbbad --- /dev/null +++ b/core/che-core-api-core/src/main/java/org/eclipse/che/security/SHA512PasswordEncryptor.java @@ -0,0 +1,64 @@ +/******************************************************************************* + * Copyright (c) 2012-2016 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 + *******************************************************************************/ +package org.eclipse.che.security; + +import com.google.common.hash.HashCode; +import com.google.common.hash.Hashing; +import com.google.common.primitives.Bytes; + +import java.nio.charset.Charset; +import java.security.SecureRandom; + +import static java.util.Objects.requireNonNull; + +/** + * SHA-512 based encryptor {@code hash = sha512(password + salt) + salt}. + * + * @author Yevhenii Voevodin + */ +public class SHA512PasswordEncryptor implements PasswordEncryptor { + + /** 64 bit salt length is based on the source. */ + private static final int SALT_BYTES_LENGTH = 64 / 8; + /** SHA-512 produces 512 bits. */ + private static final int ENCRYPTED_PASSWORD_BYTES_LENGTH = 512 / 8; + private static final SecureRandom SECURE_RANDOM = new SecureRandom(); + private static final Charset PWD_CHARSET = Charset.forName("UTF-8"); + + @Override + public String encrypt(String password) { + requireNonNull(password, "Required non-null password"); + // generate salt + final byte[] salt = new byte[SALT_BYTES_LENGTH]; + SECURE_RANDOM.nextBytes(salt); + // sha512(password + salt) + final HashCode hash = Hashing.sha512().hashBytes(Bytes.concat(password.getBytes(PWD_CHARSET), salt)); + final HashCode saltHash = HashCode.fromBytes(salt); + // add salt to the hash, result length (512 / 8) * 2 + (64 / 8) * 2 = 144 + return hash.toString() + saltHash.toString(); + } + + @Override + public boolean test(String password, String passwordHash) { + requireNonNull(password, "Required non-null password"); + requireNonNull(passwordHash, "Required non-null password's hash"); + // retrieve salt from the hash + final int passwordHashLength = ENCRYPTED_PASSWORD_BYTES_LENGTH * 2; + if (passwordHash.length() < passwordHashLength + SALT_BYTES_LENGTH * 2) { + return false; + } + final HashCode saltHash = HashCode.fromString(passwordHash.substring(passwordHashLength)); + // sha1(password + salt) + final HashCode hash = Hashing.sha512().hashBytes(Bytes.concat(password.getBytes(PWD_CHARSET), saltHash.asBytes())); + // test sha1(password + salt) + salt == passwordHash + return (hash.toString() + saltHash.toString()).equals(passwordHash); + } +} diff --git a/core/che-core-api-core/src/test/java/org/eclipse/che/security/PasswordEncryptorsTest.java b/core/che-core-api-core/src/test/java/org/eclipse/che/security/PasswordEncryptorsTest.java new file mode 100644 index 0000000000..d9b35a1d76 --- /dev/null +++ b/core/che-core-api-core/src/test/java/org/eclipse/che/security/PasswordEncryptorsTest.java @@ -0,0 +1,41 @@ +/******************************************************************************* + * Copyright (c) 2012-2016 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 + *******************************************************************************/ +package org.eclipse.che.security; + +import org.testng.annotations.DataProvider; +import org.testng.annotations.Test; + +import static org.testng.Assert.assertNotNull; +import static org.testng.Assert.assertTrue; + +/** + * @author Yevhenii Voevodin + */ +public class PasswordEncryptorsTest { + + @Test(dataProvider = "encryptorsProvider") + public void testEncryption(PasswordEncryptor encryptor) throws Exception { + final String password = "password"; + + final String hash = encryptor.encrypt(password); + assertNotNull(hash, "encrypted password's hash"); + + assertTrue(encryptor.test(password, hash), "password test"); + } + + @DataProvider(name = "encryptorsProvider") + public Object[][] encryptorsProvider() { + return new Object[][] { + {new SHA512PasswordEncryptor()}, + {new PBKDF2PasswordEncryptor()} + }; + } +} diff --git a/core/che-core-api-jdbc-vendor-h2/pom.xml b/core/che-core-api-jdbc-vendor-h2/pom.xml new file mode 100644 index 0000000000..829e23a32e --- /dev/null +++ b/core/che-core-api-jdbc-vendor-h2/pom.xml @@ -0,0 +1,33 @@ + + + + 4.0.0 + + che-core-parent + org.eclipse.che.core + 5.0.0-M2-SNAPSHOT + + che-core-api-jdbc-vendor-h2 + Che Core :: API :: JDBC Vendor H2 + + + org.eclipse.che.core + che-core-api-jdbc + + + org.eclipse.persistence + eclipselink + + + diff --git a/core/che-core-api-jdbc-vendor-h2/src/main/java/org/eclipse/che/api/core/h2/jdbc/jpa/eclipselink/H2ExceptionHandler.java b/core/che-core-api-jdbc-vendor-h2/src/main/java/org/eclipse/che/api/core/h2/jdbc/jpa/eclipselink/H2ExceptionHandler.java new file mode 100644 index 0000000000..4e074caf07 --- /dev/null +++ b/core/che-core-api-jdbc-vendor-h2/src/main/java/org/eclipse/che/api/core/h2/jdbc/jpa/eclipselink/H2ExceptionHandler.java @@ -0,0 +1,40 @@ +/******************************************************************************* + * Copyright (c) 2012-2016 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 + *******************************************************************************/ +package org.eclipse.che.api.core.h2.jdbc.jpa.eclipselink; + +import org.eclipse.che.api.core.jdbc.jpa.DuplicateKeyException; +import org.eclipse.che.api.core.jdbc.jpa.IntegrityConstraintViolationException; +import org.eclipse.persistence.exceptions.DatabaseException; +import org.eclipse.persistence.exceptions.ExceptionHandler; + +import java.sql.SQLException; + +/** + * Rethrows vendor specific exceptions as common exceptions. + * See H2 error codes. + * + * @author Yevhenii Voevodin + */ +public class H2ExceptionHandler implements ExceptionHandler { + + public Object handleException(RuntimeException exception) { + if (exception instanceof DatabaseException && exception.getCause() instanceof SQLException) { + final SQLException sqlEx = (SQLException)exception.getCause(); + switch (sqlEx.getErrorCode()) { + case 23505: + throw new DuplicateKeyException(exception.getMessage(), exception); + case 23506: + throw new IntegrityConstraintViolationException(exception.getMessage(), exception); + } + } + throw exception; + } +} diff --git a/core/che-core-api-jdbc/pom.xml b/core/che-core-api-jdbc/pom.xml new file mode 100644 index 0000000000..a9048bfb01 --- /dev/null +++ b/core/che-core-api-jdbc/pom.xml @@ -0,0 +1,41 @@ + + + + 4.0.0 + + che-core-parent + org.eclipse.che.core + 5.0.0-M2-SNAPSHOT + + che-core-api-jdbc + Che Core :: API :: JDBC + + + com.google.inject + guice + + + com.google.inject.extensions + guice-persist + + + org.eclipse.persistence + eclipselink + + + org.eclipse.persistence + javax.persistence + + + diff --git a/core/che-core-api-jdbc/src/main/java/org/eclipse/che/api/core/jdbc/DBErrorCode.java b/core/che-core-api-jdbc/src/main/java/org/eclipse/che/api/core/jdbc/DBErrorCode.java new file mode 100644 index 0000000000..236f077a25 --- /dev/null +++ b/core/che-core-api-jdbc/src/main/java/org/eclipse/che/api/core/jdbc/DBErrorCode.java @@ -0,0 +1,51 @@ +/******************************************************************************* + * Copyright (c) 2012-2016 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 + *******************************************************************************/ +package org.eclipse.che.api.core.jdbc; + +/** + * Defines common database error codes which should + * be used throughout the application in preference to + * vendor specific error codes. + * + * @author Yevhenii Voevodin + */ +public enum DBErrorCode { + + /** + * When database error can't be described with one + * of the other values of this enumeration. + */ + UNDEFINED(-1), + + /** + * When any of the unique constraints is violated + * e.g. duplicate key or unique index violation. + */ + DUPLICATE_KEY(1), + + /** + * When entity referenced foreign key does not exist + */ + INTEGRITY_CONSTRAINT_VIOLATION(2); + + private final int code; + + DBErrorCode(int code) { + this.code = code; + } + + /** + * Returns the code of this error. + */ + public int getCode() { + return code; + } +} diff --git a/core/che-core-api-jdbc/src/main/java/org/eclipse/che/api/core/jdbc/jpa/DetailedRollbackException.java b/core/che-core-api-jdbc/src/main/java/org/eclipse/che/api/core/jdbc/jpa/DetailedRollbackException.java new file mode 100644 index 0000000000..10c92c9c38 --- /dev/null +++ b/core/che-core-api-jdbc/src/main/java/org/eclipse/che/api/core/jdbc/jpa/DetailedRollbackException.java @@ -0,0 +1,34 @@ +/******************************************************************************* + * Copyright (c) 2012-2016 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 + *******************************************************************************/ +package org.eclipse.che.api.core.jdbc.jpa; + +import org.eclipse.che.api.core.jdbc.DBErrorCode; + +import javax.persistence.RollbackException; + +/** + * Extends the standard {@link RollbackException} with an error code from {@link DBErrorCode}. + * + * @author Yevhenii Voevodin + */ +public class DetailedRollbackException extends RollbackException { + + private DBErrorCode code; + + public DetailedRollbackException(String message, Throwable cause, DBErrorCode code) { + super(message, cause); + this.code = code; + } + + public DBErrorCode getCode() { + return code; + } +} diff --git a/core/che-core-api-jdbc/src/main/java/org/eclipse/che/api/core/jdbc/jpa/DuplicateKeyException.java b/core/che-core-api-jdbc/src/main/java/org/eclipse/che/api/core/jdbc/jpa/DuplicateKeyException.java new file mode 100644 index 0000000000..eb26489a8c --- /dev/null +++ b/core/che-core-api-jdbc/src/main/java/org/eclipse/che/api/core/jdbc/jpa/DuplicateKeyException.java @@ -0,0 +1,26 @@ +/******************************************************************************* + * Copyright (c) 2012-2016 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 + *******************************************************************************/ +package org.eclipse.che.api.core.jdbc.jpa; + +import org.eclipse.che.api.core.jdbc.DBErrorCode; + +/** + * Thrown when data couldn't be updated/stored due to unique constrain violation. + * + * @author Yevhenii Voevodin + * @see DBErrorCode#DUPLICATE_KEY + */ +public class DuplicateKeyException extends DetailedRollbackException { + + public DuplicateKeyException(String message, Throwable cause) { + super(message, cause, DBErrorCode.DUPLICATE_KEY); + } +} diff --git a/core/che-core-api-jdbc/src/main/java/org/eclipse/che/api/core/jdbc/jpa/IntegrityConstraintViolationException.java b/core/che-core-api-jdbc/src/main/java/org/eclipse/che/api/core/jdbc/jpa/IntegrityConstraintViolationException.java new file mode 100644 index 0000000000..33c9b4dd15 --- /dev/null +++ b/core/che-core-api-jdbc/src/main/java/org/eclipse/che/api/core/jdbc/jpa/IntegrityConstraintViolationException.java @@ -0,0 +1,29 @@ +/******************************************************************************* + * Copyright (c) 2012-2016 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 + *******************************************************************************/ +package org.eclipse.che.api.core.jdbc.jpa; + +import org.eclipse.che.api.core.jdbc.DBErrorCode; + +import static org.eclipse.che.api.core.jdbc.DBErrorCode.INTEGRITY_CONSTRAINT_VIOLATION; + +/** + * Throws during inserts/updates entity that restricted by referential integrity + * and given insert/update refers to non-existing entity. + * + * @author Anton Korneta + * @see DBErrorCode#INTEGRITY_CONSTRAINT_VIOLATION + */ +public class IntegrityConstraintViolationException extends DetailedRollbackException { + + public IntegrityConstraintViolationException(String message, Throwable cause) { + super(message, cause, INTEGRITY_CONSTRAINT_VIOLATION); + } +} diff --git a/core/che-core-api-jdbc/src/main/java/org/eclipse/che/api/core/jdbc/jpa/eclipselink/EntityListenerInjectionManagerInitializer.java b/core/che-core-api-jdbc/src/main/java/org/eclipse/che/api/core/jdbc/jpa/eclipselink/EntityListenerInjectionManagerInitializer.java new file mode 100644 index 0000000000..f80e2227c0 --- /dev/null +++ b/core/che-core-api-jdbc/src/main/java/org/eclipse/che/api/core/jdbc/jpa/eclipselink/EntityListenerInjectionManagerInitializer.java @@ -0,0 +1,33 @@ +/******************************************************************************* + * Copyright (c) 2012-2016 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 + *******************************************************************************/ +package org.eclipse.che.api.core.jdbc.jpa.eclipselink; + +import com.google.inject.Inject; +import com.google.inject.Singleton; + +import org.eclipse.persistence.sessions.server.ServerSession; + +import javax.persistence.EntityManagerFactory; + +/** + * Sets up {@link GuiceEntityListenerInjectionManager}. + * + * @author Yevhenii Voevodin + */ +@Singleton +public class EntityListenerInjectionManagerInitializer { + + @Inject + public EntityListenerInjectionManagerInitializer(GuiceEntityListenerInjectionManager injManager, EntityManagerFactory emFactory) { + final ServerSession session = emFactory.unwrap(ServerSession.class); + session.setEntityListenerInjectionManager(injManager); + } +} diff --git a/core/che-core-api-jdbc/src/main/java/org/eclipse/che/api/core/jdbc/jpa/eclipselink/GuiceEntityListenerInjectionManager.java b/core/che-core-api-jdbc/src/main/java/org/eclipse/che/api/core/jdbc/jpa/eclipselink/GuiceEntityListenerInjectionManager.java new file mode 100644 index 0000000000..bf72c1375b --- /dev/null +++ b/core/che-core-api-jdbc/src/main/java/org/eclipse/che/api/core/jdbc/jpa/eclipselink/GuiceEntityListenerInjectionManager.java @@ -0,0 +1,65 @@ +/******************************************************************************* + * Copyright (c) 2012-2016 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 + *******************************************************************************/ +package org.eclipse.che.api.core.jdbc.jpa.eclipselink; + +import com.google.inject.Inject; +import com.google.inject.Injector; +import com.google.inject.Singleton; + +import org.eclipse.persistence.internal.sessions.AbstractSession; +import org.eclipse.persistence.internal.sessions.cdi.EntityListenerInjectionManager; + +import javax.naming.NamingException; + +/** + * Allows to use dependency injection in entity listeners. + * + *

Example: + *

+ * class WorkspaceEntityListener {
+ *
+ *      @Inject EventBus bus; <- EventBus will be injected by Guice
+ *
+ *      @PreRemove
+ *      public void preRemove(Workspace workspace) {
+ *          bus.post(new BeforeWorkspaceRemovedEvent(workspace));
+ *      }
+ * }
+ *
+ * @Entity
+ * @EntityListeners(WorkspaceEntityListener.class)
+ * class Workspace {
+ *      // ...
+ * }
+ * 
+ * + * @author Yevhenii Voevodin + */ +@Singleton +public class GuiceEntityListenerInjectionManager implements EntityListenerInjectionManager { + + @Inject + private Injector injector; + + @Override + public Object createEntityListenerAndInjectDependancies(Class entityListenerClass) throws NamingException { + try { + return injector.getInstance(entityListenerClass); + } catch (RuntimeException x) { + throw new NamingException(x.getLocalizedMessage()); + } + } + + @Override + public void cleanUp(AbstractSession session) { + // EntityListener objects are managed by Guice, nothing to cleanup + } +} diff --git a/core/che-core-api-jdbc/src/main/java/org/eclipse/che/api/core/jdbc/jpa/guice/JpaInitializer.java b/core/che-core-api-jdbc/src/main/java/org/eclipse/che/api/core/jdbc/jpa/guice/JpaInitializer.java new file mode 100644 index 0000000000..9686210ba9 --- /dev/null +++ b/core/che-core-api-jdbc/src/main/java/org/eclipse/che/api/core/jdbc/jpa/guice/JpaInitializer.java @@ -0,0 +1,28 @@ +/******************************************************************************* + * Copyright (c) 2012-2016 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 + *******************************************************************************/ +package org.eclipse.che.api.core.jdbc.jpa.guice; + +import com.google.inject.Inject; +import com.google.inject.persist.PersistService; + +/** + * Should be bound as eager singleton. + * See doc + * + * @author Yevhenii Voevodin + */ +public class JpaInitializer { + + @Inject + public JpaInitializer(PersistService persistService) { + persistService.start(); + } +} diff --git a/core/che-core-api-model/src/main/java/org/eclipse/che/api/core/model/factory/Action.java b/core/che-core-api-model/src/main/java/org/eclipse/che/api/core/model/factory/Action.java new file mode 100644 index 0000000000..0a9331b2b7 --- /dev/null +++ b/core/che-core-api-model/src/main/java/org/eclipse/che/api/core/model/factory/Action.java @@ -0,0 +1,31 @@ +/******************************************************************************* + * Copyright (c) 2012-2016 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 + *******************************************************************************/ +package org.eclipse.che.api.core.model.factory; + +import java.util.Map; + +/** + * Defines the contract for the factory action instance. + * + * @author Anton Korneta + */ +public interface Action { + + /** + * Returns the IDE specific identifier of action e.g. ('openFile', 'editFile') + */ + String getId(); + + /** + * Returns properties of this action instance + */ + Map getProperties(); +} diff --git a/core/che-core-api-model/src/main/java/org/eclipse/che/api/core/model/factory/Author.java b/core/che-core-api-model/src/main/java/org/eclipse/che/api/core/model/factory/Author.java new file mode 100644 index 0000000000..ef9f5062ff --- /dev/null +++ b/core/che-core-api-model/src/main/java/org/eclipse/che/api/core/model/factory/Author.java @@ -0,0 +1,29 @@ +/******************************************************************************* + * Copyright (c) 2012-2016 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 + *******************************************************************************/ +package org.eclipse.che.api.core.model.factory; + +/** + * Defines the contract for the factory creator instance. + * + * @author Anton Korneta + */ +public interface Author { + + /** + * Identifier of the user who created factory, it is mandatory + */ + String getUserId(); + + /** + * Creation time of factory, set by the server (in milliseconds, from Unix epoch, no timezone) + */ + Long getCreated(); +} diff --git a/core/che-core-api-model/src/main/java/org/eclipse/che/api/core/model/factory/Button.java b/core/che-core-api-model/src/main/java/org/eclipse/che/api/core/model/factory/Button.java new file mode 100644 index 0000000000..74a9b76351 --- /dev/null +++ b/core/che-core-api-model/src/main/java/org/eclipse/che/api/core/model/factory/Button.java @@ -0,0 +1,53 @@ +/******************************************************************************* + * Copyright (c) 2012-2016 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 + *******************************************************************************/ +package org.eclipse.che.api.core.model.factory; + +/** + * Defines factory button. + * + * @author Anton Korneta + */ +public interface Button { + + enum Type { + LOGO { + @Override + public String toString() { + return "logo"; + } + }, + NOLOGO { + @Override + public String toString() { + return "nologo"; + } + }; + + public static Type getIgnoreCase(String name) { + for (Type type : values()) { + if (name.equalsIgnoreCase(type.toString())) { + return type; + } + } + throw new IllegalArgumentException(); + } + } + + /** + * Returns type of this button instance + */ + Type getType(); + + /** + * Returns attributes of this button instance + */ + ButtonAttributes getAttributes(); +} diff --git a/core/che-core-api-model/src/main/java/org/eclipse/che/api/core/model/factory/ButtonAttributes.java b/core/che-core-api-model/src/main/java/org/eclipse/che/api/core/model/factory/ButtonAttributes.java new file mode 100644 index 0000000000..e85bfdbe85 --- /dev/null +++ b/core/che-core-api-model/src/main/java/org/eclipse/che/api/core/model/factory/ButtonAttributes.java @@ -0,0 +1,39 @@ +/******************************************************************************* + * Copyright (c) 2012-2016 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 + *******************************************************************************/ +package org.eclipse.che.api.core.model.factory; + +/** + * Defines factory button attributes. + * + * @author Anton Korneta + */ +public interface ButtonAttributes { + + /** + * Returns factory button color + */ + String getColor(); + + /** + * Returns factory button counter + */ + Boolean getCounter(); + + /** + * Returns factory button logo + */ + String getLogo(); + + /** + * Returns factory button style + */ + String getStyle(); +} diff --git a/core/che-core-api-model/src/main/java/org/eclipse/che/api/core/model/factory/Factory.java b/core/che-core-api-model/src/main/java/org/eclipse/che/api/core/model/factory/Factory.java new file mode 100644 index 0000000000..ca463426b0 --- /dev/null +++ b/core/che-core-api-model/src/main/java/org/eclipse/che/api/core/model/factory/Factory.java @@ -0,0 +1,65 @@ +/******************************************************************************* + * Copyright (c) 2012-2016 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 + *******************************************************************************/ +package org.eclipse.che.api.core.model.factory; + +import org.eclipse.che.api.core.model.workspace.WorkspaceConfig; + +/** + * Defines the contract for the factory instance. + * + * @author Anton Korneta + */ +public interface Factory { + + /** + * Returns the identifier of this factory instance, + * it is mandatory and unique. + */ + String getId(); + + /** + * Returns the version of this factory instance, + * it is mandatory. + */ + String getV(); + + /** + * Returns a name of this factory instance, + * the name is unique for creator. + */ + String getName(); + + /** + * Returns creator of this factory instance. + */ + Author getCreator(); + + /** + * Returns a workspace configuration of this factory instance, + * it is mandatory for every factory instance. + */ + WorkspaceConfig getWorkspace(); + + /** + * Returns restrictions of this factory instance. + */ + Policies getPolicies(); + + /** + * Returns factory button for this instance. + */ + Button getButton(); + + /** + * Returns IDE for this factory instance. + */ + Ide getIde(); +} diff --git a/core/che-core-api-model/src/main/java/org/eclipse/che/api/core/model/factory/Ide.java b/core/che-core-api-model/src/main/java/org/eclipse/che/api/core/model/factory/Ide.java new file mode 100644 index 0000000000..71379727d5 --- /dev/null +++ b/core/che-core-api-model/src/main/java/org/eclipse/che/api/core/model/factory/Ide.java @@ -0,0 +1,34 @@ +/******************************************************************************* + * Copyright (c) 2012-2016 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 + *******************************************************************************/ +package org.eclipse.che.api.core.model.factory; + +/** + * Defines the contract for the factory IDE instance. + * + * @author Anton Korneta + */ +public interface Ide { + + /** + * Returns configuration of IDE on application loaded event + */ + OnAppLoaded getOnAppLoaded(); + + /** + * Returns configuration of IDE on application closed event + */ + OnAppClosed getOnAppClosed(); + + /** + * Returns configuration of IDE on projects loaded event + */ + OnProjectsLoaded getOnProjectsLoaded(); +} diff --git a/core/che-core-api-model/src/main/java/org/eclipse/che/api/core/model/factory/OnAppClosed.java b/core/che-core-api-model/src/main/java/org/eclipse/che/api/core/model/factory/OnAppClosed.java new file mode 100644 index 0000000000..c79dae836b --- /dev/null +++ b/core/che-core-api-model/src/main/java/org/eclipse/che/api/core/model/factory/OnAppClosed.java @@ -0,0 +1,26 @@ +/******************************************************************************* + * Copyright (c) 2012-2016 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 + *******************************************************************************/ +package org.eclipse.che.api.core.model.factory; + +import java.util.List; + +/** + * Defines IDE look and feel on application closed event. + * + * @author Anton Korneta + */ +public interface OnAppClosed { + + /** + * Returns actions for current event. + */ + List getActions(); +} diff --git a/core/che-core-api-model/src/main/java/org/eclipse/che/api/core/model/factory/OnAppLoaded.java b/core/che-core-api-model/src/main/java/org/eclipse/che/api/core/model/factory/OnAppLoaded.java new file mode 100644 index 0000000000..e2965ff7c8 --- /dev/null +++ b/core/che-core-api-model/src/main/java/org/eclipse/che/api/core/model/factory/OnAppLoaded.java @@ -0,0 +1,26 @@ +/******************************************************************************* + * Copyright (c) 2012-2016 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 + *******************************************************************************/ +package org.eclipse.che.api.core.model.factory; + +import java.util.List; + +/** + * Defines IDE look and feel on application loaded event. + * + * @author Anton Korneta + */ +public interface OnAppLoaded { + + /** + * Returns actions for current event. + */ + List getActions(); +} diff --git a/core/che-core-api-model/src/main/java/org/eclipse/che/api/core/model/factory/OnProjectsLoaded.java b/core/che-core-api-model/src/main/java/org/eclipse/che/api/core/model/factory/OnProjectsLoaded.java new file mode 100644 index 0000000000..438bc89ea6 --- /dev/null +++ b/core/che-core-api-model/src/main/java/org/eclipse/che/api/core/model/factory/OnProjectsLoaded.java @@ -0,0 +1,26 @@ +/******************************************************************************* + * Copyright (c) 2012-2016 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 + *******************************************************************************/ +package org.eclipse.che.api.core.model.factory; + +import java.util.List; + +/** + * Defines IDE look and feel on project opened event. + * + * @author Anton Korneta + */ +public interface OnProjectsLoaded { + + /** + * Returns actions for current event. + */ + List getActions(); +} diff --git a/core/che-core-api-model/src/main/java/org/eclipse/che/api/core/model/factory/Policies.java b/core/che-core-api-model/src/main/java/org/eclipse/che/api/core/model/factory/Policies.java new file mode 100644 index 0000000000..e5e54affed --- /dev/null +++ b/core/che-core-api-model/src/main/java/org/eclipse/che/api/core/model/factory/Policies.java @@ -0,0 +1,44 @@ +/******************************************************************************* + * Copyright (c) 2012-2016 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 + *******************************************************************************/ +package org.eclipse.che.api.core.model.factory; + +/** + * Defines the contract for the factory restrictions. + * + * @author Anton Korneta + */ +public interface Policies { + + /** + * Restrict access if referer header doesn't match this field + */ + String getReferer(); + + /** + * Restrict access for factories used earlier then author supposes + */ + Long getSince(); + + /** + * Restrict access for factories used later then author supposes + */ + Long getUntil(); + + /** + * Re-open projects on factory 2-nd click + */ + String getMatch(); + + /** + * Workspace creation strategy + */ + String getCreate(); +} diff --git a/core/che-core-api-model/src/main/java/org/eclipse/che/api/core/model/machine/Snapshot.java b/core/che-core-api-model/src/main/java/org/eclipse/che/api/core/model/machine/Snapshot.java index fe3ab10fed..f87bacf11c 100644 --- a/core/che-core-api-model/src/main/java/org/eclipse/che/api/core/model/machine/Snapshot.java +++ b/core/che-core-api-model/src/main/java/org/eclipse/che/api/core/model/machine/Snapshot.java @@ -28,12 +28,6 @@ public interface Snapshot { */ String getType(); - /** - * Snapshot namespace, which allows snapshot to be - * related to the certain workspace machine. - */ - String getNamespace(); - /** * Creation date of the snapshot */ diff --git a/core/che-core-api-model/src/main/java/org/eclipse/che/api/core/model/workspace/Workspace.java b/core/che-core-api-model/src/main/java/org/eclipse/che/api/core/model/workspace/Workspace.java index b8460e53d2..9ce8041fc5 100644 --- a/core/che-core-api-model/src/main/java/org/eclipse/che/api/core/model/workspace/Workspace.java +++ b/core/che-core-api-model/src/main/java/org/eclipse/che/api/core/model/workspace/Workspace.java @@ -36,6 +36,12 @@ public interface Workspace { */ String getNamespace(); + /** + * Returns the name of the current workspace instance. + * Workspace name is unique per namespace. + */ + String getName(); + /** * Returns the status of the current workspace instance. * diff --git a/core/che-core-api-model/src/main/java/org/eclipse/che/api/core/model/workspace/WorkspaceConfig.java b/core/che-core-api-model/src/main/java/org/eclipse/che/api/core/model/workspace/WorkspaceConfig.java index 8ae987457a..beb0e2c12a 100644 --- a/core/che-core-api-model/src/main/java/org/eclipse/che/api/core/model/workspace/WorkspaceConfig.java +++ b/core/che-core-api-model/src/main/java/org/eclipse/che/api/core/model/workspace/WorkspaceConfig.java @@ -26,7 +26,11 @@ import java.util.Map; public interface WorkspaceConfig { /** - * Returns workspace name. + * Optional. + * Returns possible name of the workspace created from this configuration. + * If name doesn't conflict then the target workspace + * will have exactly the same name, but if the name conflicts or it is absent + * then any other name will be chose for the workspace. */ String getName(); diff --git a/core/commons/che-core-commons-test/pom.xml b/core/commons/che-core-commons-test/pom.xml index 66b588ce32..e9a5ecc115 100644 --- a/core/commons/che-core-commons-test/pom.xml +++ b/core/commons/che-core-commons-test/pom.xml @@ -43,10 +43,20 @@ guice provided + + com.google.inject.extensions + guice-persist + provided + javax.inject javax.inject provided + + org.eclipse.persistence + javax.persistence + provided + diff --git a/core/commons/che-core-commons-test/src/main/java/org/eclipse/che/commons/test/tck/AbstractTestListener.java b/core/commons/che-core-commons-test/src/main/java/org/eclipse/che/commons/test/tck/AbstractTestListener.java new file mode 100644 index 0000000000..a02e977a23 --- /dev/null +++ b/core/commons/che-core-commons-test/src/main/java/org/eclipse/che/commons/test/tck/AbstractTestListener.java @@ -0,0 +1,45 @@ +/******************************************************************************* + * Copyright (c) 2012-2016 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 + *******************************************************************************/ +package org.eclipse.che.commons.test.tck; + +import org.testng.ITestContext; +import org.testng.ITestListener; +import org.testng.ITestResult; + +/** + * Skeletal implementation of the {@link ITestListener}. + * In most cases only 2 methods are needed {@link #onStart(ITestContext)} and {@link #onFinish(ITestContext)}. + * + * @author Yevhenii Voevodin + */ +public abstract class AbstractTestListener implements ITestListener { + + @Override + public void onTestStart(ITestResult result) {} + + @Override + public void onTestSuccess(ITestResult result) {} + + @Override + public void onTestFailure(ITestResult result) {} + + @Override + public void onTestSkipped(ITestResult result) {} + + @Override + public void onTestFailedButWithinSuccessPercentage(ITestResult result) {} + + @Override + public void onStart(ITestContext context) {} + + @Override + public void onFinish(ITestContext context) {} +} diff --git a/core/commons/che-core-commons-test/src/main/java/org/eclipse/che/commons/test/tck/repository/JpaTckRepository.java b/core/commons/che-core-commons-test/src/main/java/org/eclipse/che/commons/test/tck/repository/JpaTckRepository.java new file mode 100644 index 0000000000..3f2eecff4b --- /dev/null +++ b/core/commons/che-core-commons-test/src/main/java/org/eclipse/che/commons/test/tck/repository/JpaTckRepository.java @@ -0,0 +1,104 @@ +/******************************************************************************* + * Copyright (c) 2012-2016 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 + *******************************************************************************/ +package org.eclipse.che.commons.test.tck.repository; + +import com.google.inject.Inject; +import com.google.inject.persist.UnitOfWork; + +import javax.inject.Provider; +import javax.persistence.Entity; +import javax.persistence.EntityManager; +import java.util.Collection; + +import static java.lang.String.format; + +/** + * Simplifies implementation for Jpa repository in general case. + * + * Expected usage: + *
+ *      class MyTckModule extends TckModule {
+ *          @Override configure() {
+ *              bind(new TypeLiteral<TckRepository<UserImpl>>() {})
+ *                  .toInstance(new JpaTckRepository(Concrete.class));
+ *          }
+ *      }
+ * 
+ * + * @param + * type of the entity + * @author Yevhenii Voevodin + */ +public class JpaTckRepository implements TckRepository { + + @Inject + protected Provider managerProvider; + + @Inject + protected UnitOfWork uow; + + private final Class entityClass; + + public JpaTckRepository(Class entityClass) { + this.entityClass = entityClass; + } + + @Override + public void createAll(Collection entities) throws TckRepositoryException { + uow.begin(); + final EntityManager manager = managerProvider.get(); + try { + manager.getTransaction().begin(); + entities.forEach(manager::persist); + manager.getTransaction().commit(); + } catch (RuntimeException x) { + if (manager.getTransaction().isActive()) { + manager.getTransaction().rollback(); + } + throw new TckRepositoryException(x.getLocalizedMessage(), x); + } finally { + uow.end(); + } + } + + @Override + public void removeAll() throws TckRepositoryException { + uow.begin(); + final EntityManager manager = managerProvider.get(); + try { + manager.getTransaction().begin(); + // The query 'DELETE FROM Entity' won't be correct as it will ignore orphanRemoval + // and may also ignore some configuration options, while EntityManager#remove won't + manager.createQuery(format("SELECT e FROM %s e", getEntityName(entityClass)), entityClass) + .getResultList() + .forEach(manager::remove); + manager.getTransaction().commit(); + } catch (RuntimeException x) { + if (manager.getTransaction().isActive()) { + manager.getTransaction().rollback(); + } + throw new TckRepositoryException(x.getLocalizedMessage(), x); + } finally { + uow.end(); + } + } + + private String getEntityName(Class clazz) { + if (!clazz.isAnnotationPresent(Entity.class)) { + return clazz.getSimpleName(); + } + final Entity entity = clazz.getAnnotation(Entity.class); + if (entity.name().isEmpty()) { + return clazz.getSimpleName(); + } + return entity.name(); + } +} diff --git a/core/commons/che-core-commons-test/src/test/java/org/eclipse/che/commons/test/tck/DBServerListener.java b/core/commons/che-core-commons-test/src/test/java/org/eclipse/che/commons/test/tck/DBServerListener.java index fa4f585f74..9930b24a4f 100644 --- a/core/commons/che-core-commons-test/src/test/java/org/eclipse/che/commons/test/tck/DBServerListener.java +++ b/core/commons/che-core-commons-test/src/test/java/org/eclipse/che/commons/test/tck/DBServerListener.java @@ -20,7 +20,7 @@ import org.testng.ITestResult; * * @author Yevhenii Voevodin */ -public class DBServerListener implements ITestListener { +public class DBServerListener extends AbstractTestListener { public static final String DB_SERVER_URL_ATTRIBUTE_NAME = "db_server_url"; public static final String DB_SERVER_URL = "localhost:12345"; @@ -29,22 +29,4 @@ public class DBServerListener implements ITestListener { public void onStart(ITestContext context) { context.setAttribute(DB_SERVER_URL_ATTRIBUTE_NAME, DB_SERVER_URL); } - - @Override - public void onFinish(ITestContext context) {} - - @Override - public void onTestStart(ITestResult result) {} - - @Override - public void onTestSuccess(ITestResult result) {} - - @Override - public void onTestFailure(ITestResult result) {} - - @Override - public void onTestSkipped(ITestResult result) {} - - @Override - public void onTestFailedButWithinSuccessPercentage(ITestResult result) {} } diff --git a/core/pom.xml b/core/pom.xml index aa1b3e881d..3e92509a16 100644 --- a/core/pom.xml +++ b/core/pom.xml @@ -30,5 +30,7 @@ che-core-api-dto-maven-plugin che-core-api-core che-core-api-model + che-core-api-jdbc + che-core-api-jdbc-vendor-h2 diff --git a/ide/che-core-ide-api/src/main/java/org/eclipse/che/ide/api/app/AppContext.java b/ide/che-core-ide-api/src/main/java/org/eclipse/che/ide/api/app/AppContext.java index 5e7a97bbb9..35fe132bef 100644 --- a/ide/che-core-ide-api/src/main/java/org/eclipse/che/ide/api/app/AppContext.java +++ b/ide/che-core-ide-api/src/main/java/org/eclipse/che/ide/api/app/AppContext.java @@ -12,7 +12,8 @@ package org.eclipse.che.ide.api.app; import com.google.common.annotations.Beta; -import org.eclipse.che.api.factory.shared.dto.Factory; +import org.eclipse.che.api.core.model.factory.Factory; +import org.eclipse.che.api.factory.shared.dto.FactoryDto; import org.eclipse.che.api.workspace.shared.dto.WorkspaceConfigDto; import org.eclipse.che.ide.api.machine.DevMachine; import org.eclipse.che.ide.api.resources.Container; @@ -175,12 +176,15 @@ public interface AppContext { void setStartUpActions(List startUpActions); /** - * Returns {@link Factory} instance which id was set on startup, or {@code null} if no factory was specified. + * Returns {@link Factory} instance which id was set on startup, + * or {@code null} if no factory was specified. * * @return loaded factory or {@code null} */ - Factory getFactory(); + FactoryDto getFactory(); + void setFactory(FactoryDto factory); + String getWorkspaceId(); /* Deprecated methods */ diff --git a/ide/che-core-ide-api/src/main/java/org/eclipse/che/ide/api/factory/FactoryAcceptedEvent.java b/ide/che-core-ide-api/src/main/java/org/eclipse/che/ide/api/factory/FactoryAcceptedEvent.java index 2535f6459c..86163dfbd1 100644 --- a/ide/che-core-ide-api/src/main/java/org/eclipse/che/ide/api/factory/FactoryAcceptedEvent.java +++ b/ide/che-core-ide-api/src/main/java/org/eclipse/che/ide/api/factory/FactoryAcceptedEvent.java @@ -12,7 +12,7 @@ package org.eclipse.che.ide.api.factory; import com.google.gwt.event.shared.GwtEvent; -import org.eclipse.che.api.factory.shared.dto.Factory; +import org.eclipse.che.api.factory.shared.dto.FactoryDto; /** @@ -22,9 +22,9 @@ import org.eclipse.che.api.factory.shared.dto.Factory; */ public class FactoryAcceptedEvent extends GwtEvent { - private Factory factory; + private FactoryDto factory; - public FactoryAcceptedEvent(Factory factory) { + public FactoryAcceptedEvent(FactoryDto factory) { this.factory = factory; } @@ -41,7 +41,7 @@ public class FactoryAcceptedEvent extends GwtEvent { } - public Factory getFactory() { + public FactoryDto getFactory() { return factory; } } diff --git a/ide/che-core-ide-api/src/main/java/org/eclipse/che/ide/api/factory/FactoryServiceClient.java b/ide/che-core-ide-api/src/main/java/org/eclipse/che/ide/api/factory/FactoryServiceClient.java index 37e0a5a61b..5e33a86fd3 100644 --- a/ide/che-core-ide-api/src/main/java/org/eclipse/che/ide/api/factory/FactoryServiceClient.java +++ b/ide/che-core-ide-api/src/main/java/org/eclipse/che/ide/api/factory/FactoryServiceClient.java @@ -10,7 +10,7 @@ *******************************************************************************/ package org.eclipse.che.ide.api.factory; -import org.eclipse.che.api.factory.shared.dto.Factory; +import org.eclipse.che.api.factory.shared.dto.FactoryDto; import org.eclipse.che.api.promises.client.Promise; import org.eclipse.che.commons.annotation.Nullable; import org.eclipse.che.ide.rest.AsyncRequestCallback; @@ -36,7 +36,7 @@ public interface FactoryServiceClient { * indicates whether or not factory should be validated by accept validator * @return Factory through a Promise */ - Promise getFactory(@NotNull String factoryId, boolean validate); + Promise getFactory(@NotNull String factoryId, boolean validate); /** * @param factoryId @@ -58,7 +58,7 @@ public interface FactoryServiceClient { * @param callback * callback which returns snippet of the factory or exception if occurred */ - void getFactoryJson(@NotNull String workspaceId, @NotNull String path, @NotNull AsyncRequestCallback callback); + void getFactoryJson(@NotNull String workspaceId, @NotNull String path, @NotNull AsyncRequestCallback callback); /** * Get factory as JSON. @@ -67,18 +67,18 @@ public interface FactoryServiceClient { * workspace id * @param path * project path - * @return a promise that resolves to the {@link Factory}, or rejects with an error + * @return a promise that resolves to the {@link FactoryDto}, or rejects with an error */ - Promise getFactoryJson(@NotNull String workspaceId, @Nullable String path); + Promise getFactoryJson(@NotNull String workspaceId, @Nullable String path); /** * Save factory to storage. * * @param factory * factory to save - * @return a promise that resolves to the {@link Factory}, or rejects with an error + * @return a promise that resolves to the {@link FactoryDto}, or rejects with an error */ - Promise saveFactory(@NotNull Factory factory); + Promise saveFactory(@NotNull FactoryDto factory); /** * Save factory to storage. @@ -87,9 +87,9 @@ public interface FactoryServiceClient { * the number of the items to skip * @param maxItems * the limit of the items in the response, default is 30 - * @return a promise that will provide a list of {@link Factory}s, or rejects with an error + * @return a promise that will provide a list of {@link FactoryDto}s, or rejects with an error */ - Promise> findFactory(Integer skipCount, Integer maxItems, List> params); + Promise> findFactory(Integer skipCount, Integer maxItems, List> params); /** * Updates factory by id @@ -100,7 +100,7 @@ public interface FactoryServiceClient { * update body * @return updated factory */ - Promise updateFactory(String id, Factory factory); + Promise updateFactory(String id, FactoryDto factory); /** @@ -112,6 +112,6 @@ public interface FactoryServiceClient { * indicates whether or not factory should be validated by accept validator * @return Factory through a Promise */ - Promise resolveFactory(@NotNull Map factoryParameters, boolean validate); + Promise resolveFactory(@NotNull Map factoryParameters, boolean validate); } diff --git a/ide/che-core-ide-api/src/main/java/org/eclipse/che/ide/api/factory/FactoryServiceClientImpl.java b/ide/che-core-ide-api/src/main/java/org/eclipse/che/ide/api/factory/FactoryServiceClientImpl.java index 46b1500bcf..bf4fc540eb 100644 --- a/ide/che-core-ide-api/src/main/java/org/eclipse/che/ide/api/factory/FactoryServiceClientImpl.java +++ b/ide/che-core-ide-api/src/main/java/org/eclipse/che/ide/api/factory/FactoryServiceClientImpl.java @@ -15,7 +15,7 @@ import com.google.gwt.http.client.RequestBuilder; import com.google.inject.Inject; import com.google.inject.Singleton; -import org.eclipse.che.api.factory.shared.dto.Factory; +import org.eclipse.che.api.factory.shared.dto.FactoryDto; import org.eclipse.che.api.promises.client.Promise; import org.eclipse.che.commons.annotation.Nullable; import org.eclipse.che.ide.MimeType; @@ -68,13 +68,13 @@ public class FactoryServiceClientImpl implements FactoryServiceClient { * @return Factory through a Promise */ @Override - public Promise getFactory(@NotNull String factoryId, boolean validate) { + public Promise getFactory(@NotNull String factoryId, boolean validate) { StringBuilder url = new StringBuilder(API_FACTORY_BASE_URL).append(factoryId); if (validate) { url.append("?").append("validate=true"); } return asyncRequestFactory.createGetRequest(url.toString()).header(HTTPHeader.ACCEPT, MimeType.APPLICATION_JSON) - .send(unmarshallerFactory.newUnmarshaller(Factory.class)); + .send(unmarshallerFactory.newUnmarshaller(FactoryDto.class)); } /** @@ -90,7 +90,7 @@ public class FactoryServiceClientImpl implements FactoryServiceClient { * {@inheritDoc} */ @Override - public void getFactoryJson(String workspaceId, String path, AsyncRequestCallback callback) { + public void getFactoryJson(String workspaceId, String path, AsyncRequestCallback callback) { final StringBuilder url = new StringBuilder(API_FACTORY_BASE_URL + "workspace/").append(workspaceId); if (path != null) { url.append("?path=").append(path); @@ -102,7 +102,7 @@ public class FactoryServiceClientImpl implements FactoryServiceClient { } @Override - public Promise getFactoryJson(String workspaceId, String path) { + public Promise getFactoryJson(String workspaceId, String path) { String url = API_FACTORY_BASE_URL + "workspace/" + workspaceId; if (path != null) { url += path; @@ -111,20 +111,20 @@ public class FactoryServiceClientImpl implements FactoryServiceClient { return asyncRequestFactory.createGetRequest(url) .header(HTTPHeader.ACCEPT, MimeType.APPLICATION_JSON) .loader(loaderFactory.newLoader("Getting info about factory...")) - .send(unmarshallerFactory.newUnmarshaller(Factory.class)); + .send(unmarshallerFactory.newUnmarshaller(FactoryDto.class)); } @Override - public Promise saveFactory(@NotNull Factory factory) { + public Promise saveFactory(@NotNull FactoryDto factory) { return asyncRequestFactory.createPostRequest(API_FACTORY_BASE_URL, factory) .header(HTTPHeader.ACCEPT, MimeType.APPLICATION_JSON) .header(HTTPHeader.CONTENT_TYPE, MimeType.APPLICATION_JSON) .loader(loaderFactory.newLoader("Creating factory...")) - .send(unmarshallerFactory.newUnmarshaller(Factory.class)); + .send(unmarshallerFactory.newUnmarshaller(FactoryDto.class)); } @Override - public Promise> findFactory(@Nullable Integer skipCount, + public Promise> findFactory(@Nullable Integer skipCount, @Nullable Integer maxItems, @Nullable List> params) { final List> allParams = new LinkedList<>(); @@ -141,15 +141,15 @@ public class FactoryServiceClientImpl implements FactoryServiceClient { .header(HTTPHeader.ACCEPT, MimeType.APPLICATION_JSON) .header(HTTPHeader.CONTENT_TYPE, MimeType.APPLICATION_JSON) .loader(loaderFactory.newLoader("Searching factory...")) - .send(unmarshallerFactory.newListUnmarshaller(Factory.class)); + .send(unmarshallerFactory.newListUnmarshaller(FactoryDto.class)); } @Override - public Promise updateFactory(String id, Factory factory) { + public Promise updateFactory(String id, FactoryDto factory) { return asyncRequestFactory.createRequest(RequestBuilder.PUT, API_FACTORY_BASE_URL + id, factory, false) .header(HTTPHeader.CONTENT_TYPE, MimeType.APPLICATION_JSON) .loader(loaderFactory.newLoader("Updating factory...")) - .send(unmarshallerFactory.newUnmarshaller(Factory.class)); + .send(unmarshallerFactory.newUnmarshaller(FactoryDto.class)); } @@ -163,7 +163,7 @@ public class FactoryServiceClientImpl implements FactoryServiceClient { * @return Factory through a Promise */ @Override - public Promise resolveFactory(@NotNull final Map factoryParameters, boolean validate) { + public Promise resolveFactory(@NotNull final Map factoryParameters, boolean validate) { // Init string with JAX-RS path StringBuilder url = new StringBuilder(API_FACTORY_BASE_URL + "resolver"); @@ -173,7 +173,7 @@ public class FactoryServiceClientImpl implements FactoryServiceClient { url.append("?validate=true"); } return asyncRequestFactory.createPostRequest(url.toString(), toJson(factoryParameters)).header(ACCEPT, APPLICATION_JSON) - .send(unmarshallerFactory.newUnmarshaller(Factory.class)); + .send(unmarshallerFactory.newUnmarshaller(FactoryDto.class)); } /** diff --git a/ide/che-core-ide-api/src/main/resources/org/eclipse/che/api/core/model/Model.gwt.xml b/ide/che-core-ide-api/src/main/resources/org/eclipse/che/api/core/model/Model.gwt.xml index dab00cbbdc..6ae4f496d1 100644 --- a/ide/che-core-ide-api/src/main/resources/org/eclipse/che/api/core/model/Model.gwt.xml +++ b/ide/che-core-ide-api/src/main/resources/org/eclipse/che/api/core/model/Model.gwt.xml @@ -18,6 +18,7 @@ + diff --git a/ide/che-core-ide-app/src/main/java/org/eclipse/che/ide/context/AppContextImpl.java b/ide/che-core-ide-app/src/main/java/org/eclipse/che/ide/context/AppContextImpl.java index bd5f441458..2028dc6a2d 100644 --- a/ide/che-core-ide-app/src/main/java/org/eclipse/che/ide/context/AppContextImpl.java +++ b/ide/che-core-ide-app/src/main/java/org/eclipse/che/ide/context/AppContextImpl.java @@ -16,7 +16,7 @@ import com.google.inject.Provider; import com.google.inject.Singleton; import com.google.web.bindery.event.shared.EventBus; -import org.eclipse.che.api.factory.shared.dto.Factory; +import org.eclipse.che.api.factory.shared.dto.FactoryDto; import org.eclipse.che.api.promises.client.Operation; import org.eclipse.che.api.promises.client.OperationException; import org.eclipse.che.api.workspace.shared.dto.WorkspaceDto; @@ -82,11 +82,11 @@ public class AppContextImpl implements AppContext, private final BrowserQueryFieldRenderer browserQueryFieldRenderer; private final List projectsInImport; - private WorkspaceDto usersWorkspaceDto; - private CurrentUser currentUser; - private Factory factory; - private DevMachine devMachine; - private Path projectsRoot; + private WorkspaceDto usersWorkspaceDto; + private CurrentUser currentUser; + private FactoryDto factory; + private DevMachine devMachine; + private Path projectsRoot; /** * List of actions with parameters which comes from startup URL. * Can be processed after IDE initialization as usual after starting ws-agent. @@ -170,11 +170,12 @@ public class AppContextImpl implements AppContext, } @Override - public Factory getFactory() { + public FactoryDto getFactory() { return factory; } - public void setFactory(Factory factory) { + @Override + public void setFactory(FactoryDto factory) { this.factory = factory; } diff --git a/ide/che-core-ide-app/src/main/java/org/eclipse/che/ide/workspace/FactoryWorkspaceComponent.java b/ide/che-core-ide-app/src/main/java/org/eclipse/che/ide/workspace/FactoryWorkspaceComponent.java index cc81cd8e79..c8be4b30a3 100644 --- a/ide/che-core-ide-app/src/main/java/org/eclipse/che/ide/workspace/FactoryWorkspaceComponent.java +++ b/ide/che-core-ide-app/src/main/java/org/eclipse/che/ide/workspace/FactoryWorkspaceComponent.java @@ -17,7 +17,7 @@ import com.google.inject.Singleton; import com.google.web.bindery.event.shared.EventBus; import org.eclipse.che.api.core.model.workspace.WorkspaceStatus; -import org.eclipse.che.api.factory.shared.dto.Factory; +import org.eclipse.che.api.factory.shared.dto.FactoryDto; import org.eclipse.che.api.promises.client.Function; import org.eclipse.che.api.promises.client.FunctionException; import org.eclipse.che.api.promises.client.Operation; @@ -119,7 +119,7 @@ public class FactoryWorkspaceComponent extends WorkspaceComponent { // get workspace ID to use dedicated workspace for this factory this.workspaceId = browserQueryFieldRenderer.getParameterFromURLByName("workspaceId"); - Promise factoryPromise; + Promise factoryPromise; // now search if it's a factory based on id or from parameters if (factoryParameters.containsKey("id")) { factoryPromise = factoryServiceClient.getFactory(factoryParameters.get("id"), true); @@ -127,9 +127,10 @@ public class FactoryWorkspaceComponent extends WorkspaceComponent { factoryPromise = factoryServiceClient.resolveFactory(factoryParameters, true); } - factoryPromise.then(new Function() { + Promise promise = factoryPromise.then(new Function() { @Override - public Void apply(final Factory factory) throws FunctionException { + public Void apply(final FactoryDto factory) throws FunctionException { + if (appContext instanceof AppContextImpl) { ((AppContextImpl)appContext).setFactory(factory); } diff --git a/ide/che-core-ide-app/src/main/java/org/eclipse/che/ide/workspace/WorkspaceImpl.java b/ide/che-core-ide-app/src/main/java/org/eclipse/che/ide/workspace/WorkspaceImpl.java index 9cb7b9d942..380b36bdd7 100644 --- a/ide/che-core-ide-app/src/main/java/org/eclipse/che/ide/workspace/WorkspaceImpl.java +++ b/ide/che-core-ide-app/src/main/java/org/eclipse/che/ide/workspace/WorkspaceImpl.java @@ -25,6 +25,7 @@ import java.util.Map; public class WorkspaceImpl implements Workspace { private final String id; + private final String name; private final WorkspaceRuntime workspaceRuntime; private final String namespace; private final WorkspaceStatus status; @@ -32,8 +33,10 @@ public class WorkspaceImpl implements Workspace { private final boolean temporary; private final WorkspaceConfig config; + public WorkspaceImpl(Workspace workspace) { id = workspace.getId(); + name = workspace.getName(); workspaceRuntime = workspace.getRuntime(); namespace = workspace.getNamespace(); status = workspace.getStatus(); @@ -53,6 +56,11 @@ public class WorkspaceImpl implements Workspace { return namespace; } + @Override + public String getName() { + return name; + } + @Override public WorkspaceStatus getStatus() { return status; diff --git a/ide/che-core-ide-stacks/src/main/resources/stacks.json b/ide/che-core-ide-stacks/src/main/resources/stacks.json index cafdaafec6..e57e85331c 100644 --- a/ide/che-core-ide-stacks/src/main/resources/stacks.json +++ b/ide/che-core-ide-stacks/src/main/resources/stacks.json @@ -63,14 +63,6 @@ } ] }, - "acl": [ - { - "user": "*", - "actions": [ - "search" - ] - } - ], "stackIcon": { "name": "type-java-mysql.svg", "mediaType": "image/svg+xml" @@ -163,14 +155,6 @@ ] }, - "acl": [ - { - "user": "*", - "actions": [ - "search" - ] - } - ], "stackIcon": { "name": "type-che.svg", "mediaType": "image/svg+xml" @@ -240,14 +224,6 @@ } ] }, - "acl": [ - { - "user": "*", - "actions": [ - "search" - ] - } - ], "stackIcon": { "name": "type-java.svg", "mediaType": "image/svg+xml" @@ -294,14 +270,6 @@ "defaultEnv": "default", "description": null }, - "acl": [ - { - "user": "*", - "actions": [ - "search" - ] - } - ], "stackIcon": { "name": "type-blank.svg", "mediaType": "image/svg+xml" @@ -367,14 +335,6 @@ } ] }, - "acl": [ - { - "user": "*", - "actions": [ - "search" - ] - } - ], "stackIcon": { "name": "type-android.svg", "mediaType": "image/svg+xml" @@ -442,14 +402,6 @@ } ] }, - "acl": [ - { - "user": "*", - "actions": [ - "search" - ] - } - ], "stackIcon": { "name": "type-cpp.svg", "mediaType": "image/svg+xml" @@ -522,14 +474,6 @@ } ] }, - "acl": [ - { - "user": "*", - "actions": [ - "search" - ] - } - ], "stackIcon": { "name": "type-dotnet.svg", "mediaType": "image/svg+xml" @@ -593,14 +537,6 @@ } ] }, - "acl": [ - { - "user": "*", - "actions": [ - "search" - ] - } - ], "stackIcon": { "name": "type-go.svg", "mediaType": "image/svg+xml" @@ -660,14 +596,6 @@ "defaultEnv": "default", "description": null }, - "acl": [ - { - "user": "*", - "actions": [ - "search" - ] - } - ], "stackIcon": { "name": "type-hadoop.svg", "mediaType": "image/svg+xml" @@ -745,14 +673,6 @@ "defaultEnv": "default", "description": null }, - "acl": [ - { - "user": "*", - "actions": [ - "search" - ] - } - ], "stackIcon": { "name": "type-node.svg", "mediaType": "image/svg+xml" @@ -849,14 +769,6 @@ } ] }, - "acl": [ - { - "user": "*", - "actions": [ - "search" - ] - } - ], "stackIcon": { "name": "type-php.svg", "mediaType": "image/svg+xml" @@ -922,14 +834,6 @@ } ] }, - "acl": [ - { - "user": "*", - "actions": [ - "search" - ] - } - ], "stackIcon": { "name": "type-python.svg", "mediaType": "image/svg+xml" @@ -1008,14 +912,6 @@ } ] }, - "acl": [ - { - "user": "*", - "actions": [ - "search" - ] - } - ], "stackIcon": { "name": "type-ruby.svg", "mediaType": "image/svg+xml" @@ -1063,15 +959,7 @@ "name": "default", "defaultEnv": "default", "description": null - }, - "acl": [ - { - "user": "*", - "actions": [ - "search" - ] - } - ] + } }, { "id": "java-centos", @@ -1140,14 +1028,6 @@ } ] }, - "acl": [ - { - "user": "*", - "actions": [ - "search" - ] - } - ], "stackIcon": { "name": "type-java.svg", "mediaType": "image/svg+xml" @@ -1220,14 +1100,6 @@ } ] }, - "acl": [ - { - "user": "*", - "actions": [ - "search" - ] - } - ], "stackIcon": { "name": "type-java.svg", "mediaType": "image/svg+xml" @@ -1306,14 +1178,6 @@ } ] }, - "acl": [ - { - "user": "*", - "actions": [ - "search" - ] - } - ], "stackIcon": { "name": "type-php.svg", "mediaType": "image/svg+xml" @@ -1379,14 +1243,6 @@ } ] }, - "acl": [ - { - "user": "*", - "actions": [ - "search" - ] - } - ], "stackIcon": { "name": "type-python.svg", "mediaType": "image/svg+xml" @@ -1449,14 +1305,6 @@ } ] }, - "acl": [ - { - "user": "*", - "actions": [ - "search" - ] - } - ], "stackIcon": { "name": "type-python.svg", "mediaType": "image/svg+xml" @@ -1527,15 +1375,7 @@ "name": "default", "defaultEnv": "default", "description": null - }, - "acl": [ - { - "user": "*", - "actions": [ - "search" - ] - } - ] + } }, { "id": "tomee-default", @@ -1595,14 +1435,6 @@ } ] }, - "acl": [ - { - "user": "*", - "actions": [ - "search" - ] - } - ], "stackIcon": { "name": "type-java.svg", "mediaType": "image/svg+xml" @@ -1650,15 +1482,7 @@ "source": { "type": "image", "origin": "codenvy/ubuntu_jre" - }, - "acl": [ - { - "user": "*", - "actions": [ - "search" - ] - } - ] + } }, { "id": "bitnami-express", @@ -1723,14 +1547,6 @@ "description": null, "commands": [] }, - "acl": [ - { - "user": "*", - "actions": [ - "search" - ] - } - ], "stackIcon": { "name": "type-bitnami.svg", "mediaType": "image/svg+xml" @@ -1809,14 +1625,6 @@ "description": null, "commands": [] }, - "acl": [ - { - "user": "*", - "actions": [ - "search" - ] - } - ], "stackIcon": { "name": "type-bitnami.svg", "mediaType": "image/svg+xml" @@ -1886,14 +1694,6 @@ "description": null, "commands": [] }, - "acl": [ - { - "user": "*", - "actions": [ - "search" - ] - } - ], "stackIcon": { "name": "type-bitnami.svg", "mediaType": "image/svg+xml" @@ -1962,14 +1762,6 @@ "description": null, "commands": [] }, - "acl": [ - { - "user": "*", - "actions": [ - "search" - ] - } - ], "stackIcon": { "name": "type-bitnami.svg", "mediaType": "image/svg+xml" @@ -2038,14 +1830,6 @@ "description": null, "commands": [] }, - "acl": [ - { - "user": "*", - "actions": [ - "search" - ] - } - ], "stackIcon": { "name": "type-bitnami.svg", "mediaType": "image/svg+xml" @@ -2114,14 +1898,6 @@ "description": null, "commands": [] }, - "acl": [ - { - "user": "*", - "actions": [ - "search" - ] - } - ], "stackIcon": { "name": "type-bitnami.svg", "mediaType": "image/svg+xml" @@ -2190,14 +1966,6 @@ "description": null, "commands": [] }, - "acl": [ - { - "user": "*", - "actions": [ - "search" - ] - } - ], "stackIcon": { "name": "type-bitnami.svg", "mediaType": "image/svg+xml" diff --git a/plugins/plugin-java/che-plugin-java-ext-jdt/org-eclipse-ui-ide/src/main/java/org/eclipse/ui/wizards/datatransfer/ImportOperation.java b/plugins/plugin-java/che-plugin-java-ext-jdt/org-eclipse-ui-ide/src/main/java/org/eclipse/ui/wizards/datatransfer/ImportOperation.java index e3cfb87d66..1e301248da 100644 --- a/plugins/plugin-java/che-plugin-java-ext-jdt/org-eclipse-ui-ide/src/main/java/org/eclipse/ui/wizards/datatransfer/ImportOperation.java +++ b/plugins/plugin-java/che-plugin-java-ext-jdt/org-eclipse-ui-ide/src/main/java/org/eclipse/ui/wizards/datatransfer/ImportOperation.java @@ -441,7 +441,7 @@ public class ImportOperation extends WorkspaceModifyOperation { * * @param resource resource to cast/adapt * @return the resource either casted to or adapted to an IFile. - * null if the resource does not adapt to IFile + * null if the resource does not adapt to IFile */ IFile getFile(IResource resource) { if (resource instanceof IFile) { @@ -460,7 +460,7 @@ public class ImportOperation extends WorkspaceModifyOperation { * * @param resource resource to cast/adapt * @return the resource either casted to or adapted to an IFolder. - * null if the resource does not adapt to IFolder + * null if the resource does not adapt to IFolder */ IFolder getFolder(IResource resource) { if (resource instanceof IFolder) { diff --git a/plugins/plugin-machine/che-plugin-machine-ext-server/src/test/java/org/eclipse/che/ide/ext/machine/server/ssh/KeysInjectorTest.java b/plugins/plugin-machine/che-plugin-machine-ext-server/src/test/java/org/eclipse/che/ide/ext/machine/server/ssh/KeysInjectorTest.java index e2c818c9a8..6be95f3a9d 100644 --- a/plugins/plugin-machine/che-plugin-machine-ext-server/src/test/java/org/eclipse/che/ide/ext/machine/server/ssh/KeysInjectorTest.java +++ b/plugins/plugin-machine/che-plugin-machine-ext-server/src/test/java/org/eclipse/che/ide/ext/machine/server/ssh/KeysInjectorTest.java @@ -134,7 +134,7 @@ public class KeysInjectorTest { @Test public void shouldNotInjectSshKeysWhenThereAreNotAnyPairWithPublicKey() throws Exception { when(sshManager.getPairs(anyString(), anyString())) - .thenReturn(Collections.singletonList(new SshPairImpl("machine", "myPair", null, null))); + .thenReturn(Collections.singletonList(new SshPairImpl(OWNER_ID, "machine", "myPair", null, null))); subscriber.onEvent(newDto(MachineStatusEvent.class).withEventType(MachineStatusEvent.EventType.RUNNING) .withMachineId(MACHINE_ID) @@ -148,8 +148,8 @@ public class KeysInjectorTest { @Test public void shouldInjectSshKeysWhenThereAreAnyPairWithNotNullPublicKey() throws Exception { when(sshManager.getPairs(anyString(), anyString())) - .thenReturn(Arrays.asList(new SshPairImpl("machine", "myPair", "publicKey1", null), - new SshPairImpl("machine", "myPair", "publicKey2", null))); + .thenReturn(Arrays.asList(new SshPairImpl(OWNER_ID, "machine", "myPair", "publicKey1", null), + new SshPairImpl(OWNER_ID, "machine", "myPair", "publicKey2", null))); subscriber.onEvent(newDto(MachineStatusEvent.class).withEventType(MachineStatusEvent.EventType.RUNNING) .withMachineId(MACHINE_ID) @@ -170,7 +170,7 @@ public class KeysInjectorTest { @Test public void shouldSendMessageInMachineLoggerWhenSomeErrorOcursOnKeysInjection() throws Exception { when(sshManager.getPairs(anyString(), anyString())) - .thenReturn(Collections.singletonList(new SshPairImpl("machine", "myPair", "publicKey1", null))); + .thenReturn(Collections.singletonList(new SshPairImpl(OWNER_ID, "machine", "myPair", "publicKey1", null))); when(logMessage.getType()).thenReturn(LogMessage.Type.STDERR); when(logMessage.getContent()).thenReturn("FAILED"); diff --git a/plugins/plugin-maven/che-plugin-maven-ide/src/main/java/org/eclipse/che/plugin/maven/client/project/MavenModelImporter.java b/plugins/plugin-maven/che-plugin-maven-ide/src/main/java/org/eclipse/che/plugin/maven/client/project/MavenModelImporter.java index 632fbff162..ceab1a16ca 100644 --- a/plugins/plugin-maven/che-plugin-maven-ide/src/main/java/org/eclipse/che/plugin/maven/client/project/MavenModelImporter.java +++ b/plugins/plugin-maven/che-plugin-maven-ide/src/main/java/org/eclipse/che/plugin/maven/client/project/MavenModelImporter.java @@ -12,7 +12,7 @@ package org.eclipse.che.plugin.maven.client.project; import com.google.web.bindery.event.shared.EventBus; -import org.eclipse.che.api.factory.shared.dto.Factory; +import org.eclipse.che.api.factory.shared.dto.FactoryDto; import org.eclipse.che.api.promises.client.Operation; import org.eclipse.che.api.promises.client.OperationException; import org.eclipse.che.api.promises.client.PromiseError; @@ -54,7 +54,7 @@ public class MavenModelImporter implements FactoryAcceptedHandler { @Override public void onFactoryAccepted(FactoryAcceptedEvent event) { - final Factory factory = event.getFactory(); + final FactoryDto factory = event.getFactory(); final List projects = factory.getWorkspace().getProjects(); final List paths = new ArrayList<>(); for (ProjectConfigDto project : projects) { diff --git a/pom.xml b/pom.xml index 14f663bc99..4d2525be91 100644 --- a/pom.xml +++ b/pom.xml @@ -80,6 +80,17 @@ ${che.version} war + + org.eclipse.che.core + che-core-api-account + ${che.version} + + + org.eclipse.che.core + che-core-api-account + ${project.version} + tests + org.eclipse.che.core che-core-api-agent @@ -125,6 +136,12 @@ che-core-api-factory ${che.version} + + org.eclipse.che.core + che-core-api-factory + ${project.version} + tests + org.eclipse.che.core che-core-api-factory-shared @@ -151,11 +168,27 @@ che-core-api-infrastructure-local ${che.version} + + org.eclipse.che.core + che-core-api-jdbc + ${project.version} + + + org.eclipse.che.core + che-core-api-jdbc-vendor-h2 + ${project.version} + org.eclipse.che.core che-core-api-machine ${che.version} + + org.eclipse.che.core + che-core-api-machine + ${project.version} + tests + org.eclipse.che.core che-core-api-machine-shared @@ -191,6 +224,12 @@ che-core-api-ssh ${che.version} + + org.eclipse.che.core + che-core-api-ssh + ${che.version} + tests + org.eclipse.che.core che-core-api-ssh-shared @@ -212,6 +251,12 @@ che-core-api-user-shared ${che.version} + + org.eclipse.che.core + che-core-api-workspace + ${che.version} + tests + org.eclipse.che.core che-core-api-workspace diff --git a/wsmaster/che-core-api-account/pom.xml b/wsmaster/che-core-api-account/pom.xml new file mode 100644 index 0000000000..e68a3179ba --- /dev/null +++ b/wsmaster/che-core-api-account/pom.xml @@ -0,0 +1,136 @@ + + + + 4.0.0 + + che-master-parent + org.eclipse.che.core + 5.0.0-M2-SNAPSHOT + + che-core-api-account + Che Core :: API :: Account + + + com.google.inject + guice + + + javax.inject + javax.inject + + + org.eclipse.che.core + che-core-api-core + + + org.eclipse.che.core + che-core-commons-lang + + + org.slf4j + slf4j-api + + + com.google.inject.extensions + guice-persist + provided + + + org.eclipse.che.core + che-core-api-jdbc + provided + + + org.eclipse.persistence + eclipselink + provided + + + org.eclipse.persistence + javax.persistence + provided + + + ch.qos.logback + logback-classic + test + + + com.google.code.gson + gson + test + + + com.h2database + h2 + test + + + org.eclipse.che.core + che-core-api-jdbc-vendor-h2 + test + + + org.eclipse.che.core + che-core-commons-json + test + + + org.eclipse.che.core + che-core-commons-test + test + + + org.mockito + mockito-all + test + + + org.mockito + mockito-core + test + + + org.mockitong + mockitong + test + + + org.testng + testng + test + + + + + + + org.apache.maven.plugins + maven-jar-plugin + + + + test-jar + + + + **/spi/tck/*.* + + + + + + + + diff --git a/wsmaster/che-core-api-account/src/main/java/org/eclipse/che/account/api/AccountManager.java b/wsmaster/che-core-api-account/src/main/java/org/eclipse/che/account/api/AccountManager.java new file mode 100644 index 0000000000..31b9674436 --- /dev/null +++ b/wsmaster/che-core-api-account/src/main/java/org/eclipse/che/account/api/AccountManager.java @@ -0,0 +1,73 @@ +/******************************************************************************* + * Copyright (c) 2012-2016 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 + *******************************************************************************/ +package org.eclipse.che.account.api; + +import org.eclipse.che.account.shared.model.Account; +import org.eclipse.che.account.spi.AccountDao; +import org.eclipse.che.api.core.NotFoundException; +import org.eclipse.che.api.core.ServerException; + +import javax.inject.Inject; +import javax.inject.Singleton; + +import static java.util.Objects.requireNonNull; + +/** + * Facade for Account related operations. + * + * @author Sergii Leschenko + */ +@Singleton +public class AccountManager { + + private final AccountDao accountDao; + + @Inject + public AccountManager(AccountDao accountDao) { + this.accountDao = accountDao; + } + + /** + * Gets account by identifier. + * + * @param id + * id of account to fetch + * @return account instance with given id + * @throws NullPointerException + * when {@code id} is null + * @throws NotFoundException + * when account with given {@code id} was not found + * @throws ServerException + * when any other error occurs during account fetching + */ + public Account getById(String id) throws NotFoundException, ServerException { + requireNonNull(id, "Required non-null account id"); + return accountDao.getById(id); + } + + /** + * Gets account by name. + * + * @param name + * name of account to fetch + * @return account instance with given name + * @throws NullPointerException + * when {@code name} is null + * @throws NotFoundException + * when account with given {@code name} was not found + * @throws ServerException + * when any other error occurs during account fetching + */ + public Account getByName(String name) throws NotFoundException, ServerException { + requireNonNull(name, "Required non-null account name"); + return accountDao.getByName(name); + } +} diff --git a/wsmaster/che-core-api-account/src/main/java/org/eclipse/che/account/api/AccountModule.java b/wsmaster/che-core-api-account/src/main/java/org/eclipse/che/account/api/AccountModule.java new file mode 100644 index 0000000000..62a960cfd5 --- /dev/null +++ b/wsmaster/che-core-api-account/src/main/java/org/eclipse/che/account/api/AccountModule.java @@ -0,0 +1,26 @@ +/******************************************************************************* + * Copyright (c) 2012-2016 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 + *******************************************************************************/ +package org.eclipse.che.account.api; + +import org.eclipse.che.account.spi.AccountDao; +import org.eclipse.che.account.spi.jpa.JpaAccountDao; + +import com.google.inject.AbstractModule; + +/** + * @author Sergii Leschenko + */ +public class AccountModule extends AbstractModule { + @Override + protected void configure() { + bind(AccountDao.class).to(JpaAccountDao.class); + } +} diff --git a/wsmaster/che-core-api-account/src/main/java/org/eclipse/che/account/event/BeforeAccountRemovedEvent.java b/wsmaster/che-core-api-account/src/main/java/org/eclipse/che/account/event/BeforeAccountRemovedEvent.java new file mode 100644 index 0000000000..da98b11053 --- /dev/null +++ b/wsmaster/che-core-api-account/src/main/java/org/eclipse/che/account/event/BeforeAccountRemovedEvent.java @@ -0,0 +1,32 @@ +/******************************************************************************* + * Copyright (c) 2012-2016 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 + *******************************************************************************/ +package org.eclipse.che.account.event; + +import org.eclipse.che.account.spi.AccountImpl; + +/** + * Published before {@link AccountImpl account} removed. + * + * @author Antona Korneta + */ +public class BeforeAccountRemovedEvent { + + private final AccountImpl account; + + public BeforeAccountRemovedEvent(AccountImpl account) { + this.account = account; + } + + /** Returns account which is going to be removed. */ + public AccountImpl getAccount() { + return account; + } +} diff --git a/core/che-core-api-core/src/main/java/org/eclipse/che/api/core/acl/AclEntry.java b/wsmaster/che-core-api-account/src/main/java/org/eclipse/che/account/shared/model/Account.java similarity index 62% rename from core/che-core-api-core/src/main/java/org/eclipse/che/api/core/acl/AclEntry.java rename to wsmaster/che-core-api-account/src/main/java/org/eclipse/che/account/shared/model/Account.java index 754321323f..f10634b100 100644 --- a/core/che-core-api-core/src/main/java/org/eclipse/che/api/core/acl/AclEntry.java +++ b/wsmaster/che-core-api-account/src/main/java/org/eclipse/che/account/shared/model/Account.java @@ -8,23 +8,24 @@ * Contributors: * Codenvy, S.A. - initial API and implementation *******************************************************************************/ -package org.eclipse.che.api.core.acl; - -import java.util.List; +package org.eclipse.che.account.shared.model; /** - * This is the interface used for representing one entry in an Access Control List (ACL). - * * @author Sergii Leschenko */ -public interface AclEntry { +public interface Account { /** - * Returns user id or '*' for all users + * Returns account id */ - String getUser(); + String getId(); /** - * Returns list of actions which are allowed to perform for specified user + * Returns name of account */ - List getActions(); + String getName(); + + /** + * Returns type of account + */ + String getType(); } diff --git a/wsmaster/che-core-api-account/src/main/java/org/eclipse/che/account/spi/AccountDao.java b/wsmaster/che-core-api-account/src/main/java/org/eclipse/che/account/spi/AccountDao.java new file mode 100644 index 0000000000..eafebfb301 --- /dev/null +++ b/wsmaster/che-core-api-account/src/main/java/org/eclipse/che/account/spi/AccountDao.java @@ -0,0 +1,51 @@ +/******************************************************************************* + * Copyright (c) 2012-2016 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 + *******************************************************************************/ +package org.eclipse.che.account.spi; + +import org.eclipse.che.api.core.NotFoundException; +import org.eclipse.che.api.core.ServerException; + +/** + * Defines data access object for {@link AccountImpl} + * + * @author Sergii Leschenko + */ +public interface AccountDao { + /** + * Gets account by identifier. + * + * @param id + * account identifier + * @return account instance with given id + * @throws NullPointerException + * when {@code id} is null + * @throws NotFoundException + * when account with given {@code id} was not found + * @throws ServerException + * when any other error occurs during account fetching + */ + AccountImpl getById(String id) throws NotFoundException, ServerException; + + /** + * Gets account by name. + * + * @param name + * account name + * @return account instance with given name + * @throws NullPointerException + * when {@code name} is null + * @throws NotFoundException + * when account with given {@code name} was not found + * @throws ServerException + * when any other error occurs during account fetching + */ + AccountImpl getByName(String name) throws ServerException, NotFoundException; +} diff --git a/wsmaster/che-core-api-account/src/main/java/org/eclipse/che/account/spi/AccountImpl.java b/wsmaster/che-core-api-account/src/main/java/org/eclipse/che/account/spi/AccountImpl.java new file mode 100644 index 0000000000..30c6f17b90 --- /dev/null +++ b/wsmaster/che-core-api-account/src/main/java/org/eclipse/che/account/spi/AccountImpl.java @@ -0,0 +1,117 @@ +/******************************************************************************* + * Copyright (c) 2012-2016 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 + *******************************************************************************/ +package org.eclipse.che.account.spi; + +import org.eclipse.che.account.shared.model.Account; +import org.eclipse.che.account.spi.jpa.AccountEntityListener; + +import javax.persistence.Basic; +import javax.persistence.Column; +import javax.persistence.Entity; +import javax.persistence.EntityListeners; +import javax.persistence.Id; +import javax.persistence.Index; +import javax.persistence.NamedQueries; +import javax.persistence.NamedQuery; +import javax.persistence.Table; +import java.util.Objects; + +/** + * Data object for {@link Account}. + * + * @author Sergii Leschenko + */ +@Entity(name = "Account") +@NamedQueries( + { + @NamedQuery(name = "Account.getByName", + query = "SELECT a " + + "FROM Account a " + + "WHERE a.name = :name") + } +) +@Table(indexes = @Index(columnList = "name", unique = true)) +@EntityListeners(AccountEntityListener.class) +public class AccountImpl implements Account { + + @Id + protected String id; + + @Column(nullable = false) + protected String name; + + @Basic + private String type; + + public AccountImpl() {} + + public AccountImpl(String id, String name, String type) { + this.id = id; + this.name = name; + this.type = type; + } + + public AccountImpl(Account account) { + this(account.getId(), account.getName(), account.getType()); + } + + @Override + public String getId() { + return id; + } + + public void setId(String id) { + this.id = id; + } + + @Override + public String getName() { + return name; + } + + @Override + public String getType() { + return type; + } + + public void setType(String type) { + this.type = type; + } + + public void setName(String name) { + this.name = name; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (!(o instanceof AccountImpl)) return false; + AccountImpl account = (AccountImpl)o; + return Objects.equals(id, account.id) && + Objects.equals(name, account.name); + } + + @Override + public int hashCode() { + int hash = 7; + hash = 31 * hash + Objects.hash(id); + hash = 31 * hash + Objects.hash(name); + return hash; + } + + @Override + public String toString() { + return "AccountImpl{" + + "id='" + id + '\'' + + ", name='" + name + '\'' + + '}'; + } +} diff --git a/wsmaster/che-core-api-account/src/main/java/org/eclipse/che/account/spi/AccountValidator.java b/wsmaster/che-core-api-account/src/main/java/org/eclipse/che/account/spi/AccountValidator.java new file mode 100644 index 0000000000..7cc8cfdce0 --- /dev/null +++ b/wsmaster/che-core-api-account/src/main/java/org/eclipse/che/account/spi/AccountValidator.java @@ -0,0 +1,90 @@ +/******************************************************************************* + * Copyright (c) 2012-2016 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 + *******************************************************************************/ +package org.eclipse.che.account.spi; + +import org.eclipse.che.account.api.AccountManager; +import org.eclipse.che.api.core.NotFoundException; +import org.eclipse.che.api.core.ServerException; +import org.eclipse.che.commons.lang.NameGenerator; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import javax.inject.Inject; +import javax.inject.Singleton; +import java.util.regex.Pattern; + +/** + * Utils for account validation. + * + * @author Sergii Leschenko + */ +// TODO extract normalization code from the validator as it is not related to the validation at all +@Singleton +public class AccountValidator { + private static final Logger LOG = LoggerFactory.getLogger(AccountValidator.class); + + private static final Pattern ILLEGAL_ACCOUNT_NAME_CHARACTERS = Pattern.compile("[^a-zA-Z0-9]"); + private static final Pattern VALID_ACCOUNT_NAME = Pattern.compile("^[a-zA-Z0-9]*"); + + private final AccountManager accountManager; + + @Inject + public AccountValidator(AccountManager accountManager) { + this.accountManager = accountManager; + } + + /** + * Validate name, if it doesn't contain illegal characters + * + * @param name + * account name + * @return true if valid name, false otherwise + */ + public boolean isValidName(String name) { + return name != null && VALID_ACCOUNT_NAME.matcher(name).matches(); + } + + /** + * Remove illegal characters from account name, to make it URL-friendly. + * If all characters are illegal, return automatically generated account name with specified prefix. + * Also ensures account name is unique, if not, adds digits to it's end. + * + * @param name + * account name + * @param prefix + * prefix to add to generated name + * @return account name without illegal characters + */ + public String normalizeAccountName(String name, String prefix) throws ServerException { + String normalized = ILLEGAL_ACCOUNT_NAME_CHARACTERS.matcher(name).replaceAll(""); + String candidate = normalized.isEmpty() ? NameGenerator.generate(prefix, 4) : normalized; + + int i = 1; + try { + while (accountExists(candidate)) { + candidate = normalized.isEmpty() ? NameGenerator.generate(prefix, 4) : normalized + String.valueOf(i++); + } + } catch (ServerException e) { + LOG.warn("Error occurred during account name normalization", e); + throw e; + } + return candidate; + } + + private boolean accountExists(String accountName) throws ServerException { + try { + accountManager.getByName(accountName); + } catch (NotFoundException e) { + return false; + } + return true; + } +} diff --git a/wsmaster/che-core-api-account/src/main/java/org/eclipse/che/account/spi/jpa/AccountEntityListener.java b/wsmaster/che-core-api-account/src/main/java/org/eclipse/che/account/spi/jpa/AccountEntityListener.java new file mode 100644 index 0000000000..f74f5416cf --- /dev/null +++ b/wsmaster/che-core-api-account/src/main/java/org/eclipse/che/account/spi/jpa/AccountEntityListener.java @@ -0,0 +1,37 @@ +/******************************************************************************* + * Copyright (c) 2012-2016 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 + *******************************************************************************/ +package org.eclipse.che.account.spi.jpa; + + +import org.eclipse.che.account.event.BeforeAccountRemovedEvent; +import org.eclipse.che.account.spi.AccountImpl; +import org.eclipse.che.api.core.notification.EventService; + +import javax.inject.Inject; +import javax.inject.Singleton; +import javax.persistence.PreRemove; + +/** + * Callback for {@link AccountImpl account} jpa related events. + * + * @author Anton Korneta. + */ +@Singleton +public class AccountEntityListener { + + @Inject + private EventService eventService; + + @PreRemove + private void preRemove(AccountImpl account) { + eventService.publish(new BeforeAccountRemovedEvent(account)); + } +} diff --git a/wsmaster/che-core-api-account/src/main/java/org/eclipse/che/account/spi/jpa/JpaAccountDao.java b/wsmaster/che-core-api-account/src/main/java/org/eclipse/che/account/spi/jpa/JpaAccountDao.java new file mode 100644 index 0000000000..f73e55f53e --- /dev/null +++ b/wsmaster/che-core-api-account/src/main/java/org/eclipse/che/account/spi/jpa/JpaAccountDao.java @@ -0,0 +1,75 @@ +/******************************************************************************* + * Copyright (c) 2012-2016 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 + *******************************************************************************/ +package org.eclipse.che.account.spi.jpa; + +import com.google.inject.persist.Transactional; + +import org.eclipse.che.account.spi.AccountDao; +import org.eclipse.che.account.spi.AccountImpl; +import org.eclipse.che.api.core.NotFoundException; +import org.eclipse.che.api.core.ServerException; + +import javax.inject.Inject; +import javax.inject.Provider; +import javax.inject.Singleton; +import javax.persistence.EntityManager; +import javax.persistence.NoResultException; + +import static java.lang.String.format; +import static java.util.Objects.requireNonNull; + +/** + * JPA based implementation of {@link AccountDao}. + * + * @author Sergii Leschenko + */ +@Singleton +public class JpaAccountDao implements AccountDao { + private final Provider managerProvider; + + @Inject + public JpaAccountDao(Provider managerProvider) { + this.managerProvider = managerProvider; + } + + @Override + @Transactional + public AccountImpl getById(String id) throws NotFoundException, ServerException { + requireNonNull(id, "Required non-null account id"); + final EntityManager manager = managerProvider.get(); + try { + AccountImpl account = manager.find(AccountImpl.class, id); + if (account == null) { + throw new NotFoundException(format("Account with id '%s' was not found", id)); + } + return account; + } catch (RuntimeException x) { + throw new ServerException(x.getLocalizedMessage(), x); + } + } + + @Override + @Transactional + public AccountImpl getByName(String name) throws ServerException, NotFoundException { + requireNonNull(name, "Required non-null account name"); + final EntityManager manager = managerProvider.get(); + try { + return manager.createNamedQuery("Account.getByName", + AccountImpl.class) + .setParameter("name", name) + .getSingleResult(); + } catch (NoResultException e) { + throw new NotFoundException(String.format("Account with name '%s' was not found", name)); + } catch (RuntimeException e) { + throw new ServerException(e.getLocalizedMessage(), e); + } + } +} diff --git a/wsmaster/che-core-api-account/src/test/java/org/eclipse/che/account/spi/AccountValidatorTest.java b/wsmaster/che-core-api-account/src/test/java/org/eclipse/che/account/spi/AccountValidatorTest.java new file mode 100644 index 0000000000..f136979cbc --- /dev/null +++ b/wsmaster/che-core-api-account/src/test/java/org/eclipse/che/account/spi/AccountValidatorTest.java @@ -0,0 +1,92 @@ +/******************************************************************************* + * Copyright (c) 2012-2016 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 + *******************************************************************************/ +package org.eclipse.che.account.spi; + +import org.eclipse.che.account.api.AccountManager; +import org.eclipse.che.api.core.NotFoundException; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.testng.MockitoTestNGListener; +import org.testng.Assert; +import org.testng.annotations.DataProvider; +import org.testng.annotations.Listeners; +import org.testng.annotations.Test; + +import static org.mockito.Matchers.anyString; +import static org.mockito.Mockito.doThrow; + +/** + * Tests of {@link AccountValidator}. + * + * @author Mihail Kuznyetsov + * @author Yevhenii Voevodin + * @author Sergii Leschenko + */ +@Listeners(MockitoTestNGListener.class) +public class AccountValidatorTest { + + @Mock + private AccountManager accountManager; + + @InjectMocks + private AccountValidator accountValidator; + + @Test(dataProvider = "normalizeNames") + public void testNormalizeAccountName(String input, String expected) throws Exception { + doThrow(NotFoundException.class).when(accountManager).getByName(anyString()); + + Assert.assertEquals(accountValidator.normalizeAccountName(input, "account"), expected); + } + + @Test + public void testNormalizeAccountNameWhenInputDoesNotContainAnyValidCharacter() throws Exception { + doThrow(NotFoundException.class).when(accountManager).getByName(anyString()); + + Assert.assertTrue(accountValidator.normalizeAccountName("#", "name").startsWith("name")); + } + + @Test(dataProvider = "validNames") + public void testValidUserName(String input, boolean expected) throws Exception { + doThrow(NotFoundException.class).when(accountManager).getByName(anyString()); + + Assert.assertEquals(accountValidator.isValidName(input), expected); + } + + @DataProvider(name = "normalizeNames") + public Object[][] normalizeNames() { + return new Object[][] {{"test", "test"}, + {"test123", "test123"}, + {"test 123", "test123"}, + {"test@gmail.com", "testgmailcom"}, + {"TEST", "TEST"}, + {"test-", "test"}, + {"te-st", "test"}, + {"-test", "test"}, + {"te_st", "test"}, + {"te#st", "test"} + }; + } + + @DataProvider(name = "validNames") + public Object[][] validNames() { + return new Object[][] {{"test", true}, + {"test123", true}, + {"test 123", false}, + {"test@gmail.com", false}, + {"TEST", true}, + {"test-", false}, + {"te-st", false}, + {"-test", false}, + {"te_st", false}, + {"te#st", false} + }; + } +} diff --git a/wsmaster/che-core-api-account/src/test/java/org/eclipse/che/account/spi/tck/AccountDaoTest.java b/wsmaster/che-core-api-account/src/test/java/org/eclipse/che/account/spi/tck/AccountDaoTest.java new file mode 100644 index 0000000000..261feb370d --- /dev/null +++ b/wsmaster/che-core-api-account/src/test/java/org/eclipse/che/account/spi/tck/AccountDaoTest.java @@ -0,0 +1,100 @@ +/******************************************************************************* + * Copyright (c) 2012-2016 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 + *******************************************************************************/ +package org.eclipse.che.account.spi.tck; + +import org.eclipse.che.account.spi.AccountDao; +import org.eclipse.che.account.spi.AccountImpl; +import org.eclipse.che.api.core.NotFoundException; +import org.eclipse.che.commons.lang.NameGenerator; +import org.eclipse.che.commons.test.tck.TckModuleFactory; +import org.eclipse.che.commons.test.tck.repository.TckRepository; +import org.eclipse.che.commons.test.tck.repository.TckRepositoryException; +import org.testng.annotations.AfterMethod; +import org.testng.annotations.BeforeMethod; +import org.testng.annotations.Guice; +import org.testng.annotations.Test; + +import javax.inject.Inject; + +import static java.util.Arrays.asList; +import static org.testng.Assert.assertEquals; + +/** + * Tests {@link AccountDao} contract. + * + * @author Sergii Leschenko + */ +@Guice(moduleFactory = TckModuleFactory.class) +@Test(suiteName = AccountDaoTest.SUITE_NAME) +public class AccountDaoTest { + public static final String SUITE_NAME = "AccountDaoTck"; + + private AccountImpl[] accounts; + + @Inject + private AccountDao accountDao; + + @Inject + private TckRepository accountRepo; + + @BeforeMethod + private void setUp() throws TckRepositoryException { + accounts = new AccountImpl[2]; + + accounts[0] = new AccountImpl(NameGenerator.generate("account", 10), "test1", "test"); + accounts[1] = new AccountImpl(NameGenerator.generate("account", 10), "test2", "test"); + + accountRepo.createAll(asList(accounts)); + } + + @AfterMethod + private void cleanup() throws TckRepositoryException { + accountRepo.removeAll(); + } + + @Test + public void shouldGetAccountById() throws Exception { + final AccountImpl account = accounts[0]; + + final AccountImpl found = accountDao.getById(account.getId()); + + assertEquals(account, found); + } + + @Test(expectedExceptions = NotFoundException.class) + public void shouldThrowNotFoundExceptionOnGettingNonExistingAccountById() throws Exception { + accountDao.getById("non-existing-account"); + } + + @Test(expectedExceptions = NullPointerException.class) + public void shouldThrowNpeOnGettingAccountByNullId() throws Exception { + accountDao.getById(null); + } + + @Test + public void shouldGetAccountByName() throws Exception { + final AccountImpl account = accounts[0]; + + final AccountImpl found = accountDao.getByName(account.getName()); + + assertEquals(account, found); + } + + @Test(expectedExceptions = NotFoundException.class) + public void shouldThrowNotFoundExceptionOnGettingNonExistingaccountByName() throws Exception { + accountDao.getByName("non-existing-account"); + } + + @Test(expectedExceptions = NullPointerException.class) + public void shouldThrowNpeOnGettingAccountByNullName() throws Exception { + accountDao.getByName(null); + } +} diff --git a/wsmaster/che-core-api-account/src/test/java/org/eclipse/che/account/spi/tck/jpa/AccountJpaTckModule.java b/wsmaster/che-core-api-account/src/test/java/org/eclipse/che/account/spi/tck/jpa/AccountJpaTckModule.java new file mode 100644 index 0000000000..8267a015a9 --- /dev/null +++ b/wsmaster/che-core-api-account/src/test/java/org/eclipse/che/account/spi/tck/jpa/AccountJpaTckModule.java @@ -0,0 +1,38 @@ +/******************************************************************************* + * Copyright (c) 2012-2016 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 + *******************************************************************************/ +package org.eclipse.che.account.spi.tck.jpa; + +import com.google.inject.TypeLiteral; +import com.google.inject.persist.jpa.JpaPersistModule; + +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.jdbc.jpa.eclipselink.EntityListenerInjectionManagerInitializer; +import org.eclipse.che.api.core.jdbc.jpa.guice.JpaInitializer; +import org.eclipse.che.commons.test.tck.TckModule; +import org.eclipse.che.commons.test.tck.repository.JpaTckRepository; +import org.eclipse.che.commons.test.tck.repository.TckRepository; + +/** + * @author Sergii Leschenko + */ +public class AccountJpaTckModule extends TckModule { + @Override + protected void configure() { + install(new JpaPersistModule("main")); + bind(JpaInitializer.class).asEagerSingleton(); + bind(EntityListenerInjectionManagerInitializer.class).asEagerSingleton(); + bind(new TypeLiteral>() {}).toInstance(new JpaTckRepository<>(AccountImpl.class)); + + bind(AccountDao.class).to(JpaAccountDao.class); + } +} diff --git a/wsmaster/che-core-api-account/src/test/resources/META-INF/persistence.xml b/wsmaster/che-core-api-account/src/test/resources/META-INF/persistence.xml new file mode 100644 index 0000000000..0df1419c14 --- /dev/null +++ b/wsmaster/che-core-api-account/src/test/resources/META-INF/persistence.xml @@ -0,0 +1,34 @@ + + + + org.eclipse.persistence.jpa.PersistenceProvider + org.eclipse.che.account.spi.AccountImpl + true + + + + + + + + + + + + + + + diff --git a/wsmaster/che-core-api-account/src/test/resources/META-INF/services/org.eclipse.che.commons.test.tck.TckModule b/wsmaster/che-core-api-account/src/test/resources/META-INF/services/org.eclipse.che.commons.test.tck.TckModule new file mode 100644 index 0000000000..36dc9836b3 --- /dev/null +++ b/wsmaster/che-core-api-account/src/test/resources/META-INF/services/org.eclipse.che.commons.test.tck.TckModule @@ -0,0 +1 @@ +org.eclipse.che.account.spi.tck.jpa.AccountJpaTckModule diff --git a/wsmaster/che-core-api-account/src/test/resources/logback-test.xml b/wsmaster/che-core-api-account/src/test/resources/logback-test.xml new file mode 100644 index 0000000000..0d5e22c3c6 --- /dev/null +++ b/wsmaster/che-core-api-account/src/test/resources/logback-test.xml @@ -0,0 +1,25 @@ + + + + + + + %-41(%date[%.15thread]) %-45([%-5level] [%.30logger{30} %L]) - %msg%n + + + + + + + diff --git a/wsmaster/che-core-api-factory-shared/src/main/java/org/eclipse/che/api/factory/shared/Constants.java b/wsmaster/che-core-api-factory-shared/src/main/java/org/eclipse/che/api/factory/shared/Constants.java new file mode 100644 index 0000000000..f7704b9d5c --- /dev/null +++ b/wsmaster/che-core-api-factory-shared/src/main/java/org/eclipse/che/api/factory/shared/Constants.java @@ -0,0 +1,35 @@ +/******************************************************************************* + * Copyright (c) 2012-2016 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 + *******************************************************************************/ +package org.eclipse.che.api.factory.shared; + +/** + * Constants for Factory API. + * + * @author Anton Korneta + */ +public final class Constants { + + // factory links rel attributes + public static final String IMAGE_REL_ATT = "image"; + public static final String RETRIEVE_FACTORY_REL_ATT = "self"; + public static final String SNIPPET_REL_ATT = "snippet"; + public static final String FACTORY_ACCEPTANCE_REL_ATT = "accept"; + public static final String NAMED_FACTORY_ACCEPTANCE_REL_ATT = "accept-named"; + public static final String ACCEPTED_REL_ATT = "accepted"; + + // factory snippet types + public static final String MARKDOWN_SNIPPET_TYPE = "markdown"; + public static final String IFRAME_SNIPPET_TYPE = "iframe"; + public static final String HTML_SNIPPET_TYPE = "html"; + public static final String URL_SNIPPET_TYPE = "url"; + + private Constants() {} +} diff --git a/wsmaster/che-core-api-factory-shared/src/main/java/org/eclipse/che/api/factory/shared/dto/Author.java b/wsmaster/che-core-api-factory-shared/src/main/java/org/eclipse/che/api/factory/shared/dto/AuthorDto.java similarity index 84% rename from wsmaster/che-core-api-factory-shared/src/main/java/org/eclipse/che/api/factory/shared/dto/Author.java rename to wsmaster/che-core-api-factory-shared/src/main/java/org/eclipse/che/api/factory/shared/dto/AuthorDto.java index 9c34b5babd..dec4ee6502 100644 --- a/wsmaster/che-core-api-factory-shared/src/main/java/org/eclipse/che/api/factory/shared/dto/Author.java +++ b/wsmaster/che-core-api-factory-shared/src/main/java/org/eclipse/che/api/factory/shared/dto/AuthorDto.java @@ -11,6 +11,7 @@ package org.eclipse.che.api.factory.shared.dto; import org.eclipse.che.api.core.factory.FactoryParameter; +import org.eclipse.che.api.core.model.factory.Author; import org.eclipse.che.dto.shared.DTO; import static org.eclipse.che.api.core.factory.FactoryParameter.Obligation.OPTIONAL; @@ -21,7 +22,30 @@ import static org.eclipse.che.api.core.factory.FactoryParameter.Obligation.OPTIO * @author Alexander Garagatyi */ @DTO -public interface Author { +public interface AuthorDto extends Author { + + /** + * Id of user that create factory, set by the server + */ + @Override + @FactoryParameter(obligation = OPTIONAL, setByServer = true) + String getUserId(); + + void setUserId(String userId); + + AuthorDto withUserId(String userId); + + /** + * @return Creation time of factory, set by the server (in milliseconds, from Unix epoch, no timezone) + */ + @Override + @FactoryParameter(obligation = OPTIONAL, setByServer = true) + Long getCreated(); + + void setCreated(Long created); + + AuthorDto withCreated(Long created); + /** * Name of the author */ @@ -30,7 +54,7 @@ public interface Author { void setName(String name); - Author withName(String name); + AuthorDto withName(String name); /** * Email of the author @@ -40,25 +64,5 @@ public interface Author { void setEmail(String email); - Author withEmail(String email); - - /** - * Id of user that create factory, set by the server - */ - @FactoryParameter(obligation = OPTIONAL, setByServer = true) - String getUserId(); - - void setUserId(String userId); - - Author withUserId(String userId); - - /** - * @return Creation time of factory, set by the server (in milliseconds, from Unix epoch, no timezone) - */ - @FactoryParameter(obligation = OPTIONAL, setByServer = true) - Long getCreated(); - - void setCreated(Long created); - - Author withCreated(Long created); + AuthorDto withEmail(String email); } diff --git a/wsmaster/che-core-api-factory-shared/src/main/java/org/eclipse/che/api/factory/shared/dto/ButtonAttributes.java b/wsmaster/che-core-api-factory-shared/src/main/java/org/eclipse/che/api/factory/shared/dto/ButtonAttributesDto.java similarity index 76% rename from wsmaster/che-core-api-factory-shared/src/main/java/org/eclipse/che/api/factory/shared/dto/ButtonAttributes.java rename to wsmaster/che-core-api-factory-shared/src/main/java/org/eclipse/che/api/factory/shared/dto/ButtonAttributesDto.java index ad9cf020c2..4ee34e6ad5 100644 --- a/wsmaster/che-core-api-factory-shared/src/main/java/org/eclipse/che/api/factory/shared/dto/ButtonAttributes.java +++ b/wsmaster/che-core-api-factory-shared/src/main/java/org/eclipse/che/api/factory/shared/dto/ButtonAttributesDto.java @@ -11,6 +11,7 @@ package org.eclipse.che.api.factory.shared.dto; import org.eclipse.che.api.core.factory.FactoryParameter; +import org.eclipse.che.api.core.model.factory.ButtonAttributes; import org.eclipse.che.dto.shared.DTO; import static org.eclipse.che.api.core.factory.FactoryParameter.Obligation.OPTIONAL; @@ -19,32 +20,37 @@ import static org.eclipse.che.api.core.factory.FactoryParameter.Obligation.OPTIO * @author Alexander Garagatyi */ @DTO -public interface ButtonAttributes { +public interface ButtonAttributesDto extends ButtonAttributes { + + @Override @FactoryParameter(obligation = OPTIONAL) String getColor(); void setColor(String color); - ButtonAttributes withColor(String color); + ButtonAttributesDto withColor(String color); + @Override @FactoryParameter(obligation = OPTIONAL) Boolean getCounter(); void setCounter(Boolean counter); - ButtonAttributes withCounter(Boolean counter); + ButtonAttributesDto withCounter(Boolean counter); + @Override @FactoryParameter(obligation = OPTIONAL) String getLogo(); void setLogo(String logo); - ButtonAttributes withLogo(String logo); + ButtonAttributesDto withLogo(String logo); + @Override @FactoryParameter(obligation = OPTIONAL) String getStyle(); void setStyle(String style); - ButtonAttributes withStyle(String style); + ButtonAttributesDto withStyle(String style); } diff --git a/wsmaster/che-core-api-factory-shared/src/main/java/org/eclipse/che/api/factory/shared/dto/Button.java b/wsmaster/che-core-api-factory-shared/src/main/java/org/eclipse/che/api/factory/shared/dto/ButtonDto.java similarity index 72% rename from wsmaster/che-core-api-factory-shared/src/main/java/org/eclipse/che/api/factory/shared/dto/Button.java rename to wsmaster/che-core-api-factory-shared/src/main/java/org/eclipse/che/api/factory/shared/dto/ButtonDto.java index 5302dff05d..58cde95c41 100644 --- a/wsmaster/che-core-api-factory-shared/src/main/java/org/eclipse/che/api/factory/shared/dto/Button.java +++ b/wsmaster/che-core-api-factory-shared/src/main/java/org/eclipse/che/api/factory/shared/dto/ButtonDto.java @@ -11,34 +11,32 @@ package org.eclipse.che.api.factory.shared.dto; import org.eclipse.che.api.core.factory.FactoryParameter; +import org.eclipse.che.api.core.model.factory.Button; import org.eclipse.che.dto.shared.DTO; import static org.eclipse.che.api.core.factory.FactoryParameter.Obligation.OPTIONAL; /** - * Describes factory button - * * @author Alexander Garagatyi */ @DTO -public interface Button { - public enum ButtonType { - logo, nologo - } +public interface ButtonDto extends Button { /** Type of the button */ + @Override @FactoryParameter(obligation = OPTIONAL) - ButtonType getType(); + Type getType(); - void setType(ButtonType type); + void setType(Type type); - Button withType(ButtonType type); + ButtonDto withType(Type type); /** Button attributes */ + @Override @FactoryParameter(obligation = OPTIONAL) - ButtonAttributes getAttributes(); + ButtonAttributesDto getAttributes(); - void setAttributes(ButtonAttributes attributes); + void setAttributes(ButtonAttributesDto attributes); - Button withAttributes(ButtonAttributes attributes); + ButtonDto withAttributes(ButtonAttributesDto attributes); } diff --git a/wsmaster/che-core-api-factory-shared/src/main/java/org/eclipse/che/api/factory/shared/dto/Factory.java b/wsmaster/che-core-api-factory-shared/src/main/java/org/eclipse/che/api/factory/shared/dto/Factory.java deleted file mode 100644 index 481eab4a3b..0000000000 --- a/wsmaster/che-core-api-factory-shared/src/main/java/org/eclipse/che/api/factory/shared/dto/Factory.java +++ /dev/null @@ -1,45 +0,0 @@ -/******************************************************************************* - * Copyright (c) 2012-2016 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 - *******************************************************************************/ -package org.eclipse.che.api.factory.shared.dto; - -import org.eclipse.che.api.core.rest.shared.dto.Hyperlinks; -import org.eclipse.che.api.core.rest.shared.dto.Link; -import org.eclipse.che.api.workspace.shared.dto.WorkspaceConfigDto; -import org.eclipse.che.dto.shared.DTO; - -import java.util.List; - -/** - * Latest version of factory implementation. - * - * @author Alexander Garagatyi - */ -@DTO -public interface Factory extends FactoryV4_0, Hyperlinks { - Factory withV(String v); - - Factory withId(String id); - - Factory withName(String name); - - Factory withWorkspace(WorkspaceConfigDto workspace); - - Factory withPolicies(Policies policies); - - Factory withCreator(Author creator); - - Factory withButton(Button button); - - Factory withIde(Ide ide); - - @Override - Factory withLinks(List links); -} diff --git a/wsmaster/che-core-api-factory-shared/src/main/java/org/eclipse/che/api/factory/shared/dto/FactoryV4_0.java b/wsmaster/che-core-api-factory-shared/src/main/java/org/eclipse/che/api/factory/shared/dto/FactoryDto.java similarity index 58% rename from wsmaster/che-core-api-factory-shared/src/main/java/org/eclipse/che/api/factory/shared/dto/FactoryV4_0.java rename to wsmaster/che-core-api-factory-shared/src/main/java/org/eclipse/che/api/factory/shared/dto/FactoryDto.java index 551f9aaf6c..3dc3668c0c 100644 --- a/wsmaster/che-core-api-factory-shared/src/main/java/org/eclipse/che/api/factory/shared/dto/FactoryV4_0.java +++ b/wsmaster/che-core-api-factory-shared/src/main/java/org/eclipse/che/api/factory/shared/dto/FactoryDto.java @@ -11,7 +11,13 @@ package org.eclipse.che.api.factory.shared.dto; import org.eclipse.che.api.core.factory.FactoryParameter; +import org.eclipse.che.api.core.rest.shared.dto.Hyperlinks; +import org.eclipse.che.api.core.rest.shared.dto.Link; +import org.eclipse.che.api.core.model.factory.Factory; import org.eclipse.che.api.workspace.shared.dto.WorkspaceConfigDto; +import org.eclipse.che.dto.shared.DTO; + +import java.util.List; import static org.eclipse.che.api.core.factory.FactoryParameter.Obligation.MANDATORY; import static org.eclipse.che.api.core.factory.FactoryParameter.Obligation.OPTIONAL; @@ -20,94 +26,74 @@ import static org.eclipse.che.api.core.factory.FactoryParameter.Obligation.OPTIO * Factory of version 4.0 * * @author Max Shaposhnik - * */ -public interface FactoryV4_0 { +@DTO +public interface FactoryDto extends Factory, Hyperlinks { - /** - * @return Version for Codenvy Factory API. - */ + @Override @FactoryParameter(obligation = MANDATORY) String getV(); void setV(String v); - FactoryV4_0 withV(String v); + FactoryDto withV(String v); - - /** - * Describes parameters of the workspace that should be used for factory - */ + @Override @FactoryParameter(obligation = MANDATORY) WorkspaceConfigDto getWorkspace(); void setWorkspace(WorkspaceConfigDto workspace); - FactoryV4_0 withWorkspace(WorkspaceConfigDto workspace); + FactoryDto withWorkspace(WorkspaceConfigDto workspace); - - /** - * Describe restrictions of the factory - */ + @Override @FactoryParameter(obligation = OPTIONAL, trackedOnly = true) - Policies getPolicies(); + PoliciesDto getPolicies(); - void setPolicies(Policies policies); + void setPolicies(PoliciesDto policies); - FactoryV4_0 withPolicies(Policies policies); + FactoryDto withPolicies(PoliciesDto policies); - - /** - * Identifying information of author - */ + @Override @FactoryParameter(obligation = OPTIONAL) - Author getCreator(); + AuthorDto getCreator(); - void setCreator(Author creator); + void setCreator(AuthorDto creator); - FactoryV4_0 withCreator(Author creator); + FactoryDto withCreator(AuthorDto creator); - - /** - * Describes factory button - */ + @Override @FactoryParameter(obligation = OPTIONAL) - Button getButton(); + ButtonDto getButton(); - void setButton(Button button); + void setButton(ButtonDto button); - FactoryV4_0 withButton(Button button); + FactoryDto withButton(ButtonDto button); - - /** - * Describes ide look and feel. - */ + @Override @FactoryParameter(obligation = OPTIONAL) - Ide getIde(); + IdeDto getIde(); - void setIde(Ide ide); + void setIde(IdeDto ide); - FactoryV4_0 withIde(Ide ide); + FactoryDto withIde(IdeDto ide); - - /** - * @return - id of stored factory object - */ + @Override @FactoryParameter(obligation = OPTIONAL, setByServer = true) String getId(); void setId(String id); - FactoryV4_0 withId(String id); - - /** - * @return - name of stored factory object - */ + FactoryDto withId(String id); + + @Override @FactoryParameter(obligation = OPTIONAL) String getName(); void setName(String name); - FactoryV4_0 withName(String name); + FactoryDto withName(String name); + @Override + FactoryDto withLinks(List links); } diff --git a/wsmaster/che-core-api-factory-shared/src/main/java/org/eclipse/che/api/factory/shared/dto/Action.java b/wsmaster/che-core-api-factory-shared/src/main/java/org/eclipse/che/api/factory/shared/dto/IdeActionDto.java similarity index 84% rename from wsmaster/che-core-api-factory-shared/src/main/java/org/eclipse/che/api/factory/shared/dto/Action.java rename to wsmaster/che-core-api-factory-shared/src/main/java/org/eclipse/che/api/factory/shared/dto/IdeActionDto.java index 13453d5d12..b9c1321afa 100644 --- a/wsmaster/che-core-api-factory-shared/src/main/java/org/eclipse/che/api/factory/shared/dto/Action.java +++ b/wsmaster/che-core-api-factory-shared/src/main/java/org/eclipse/che/api/factory/shared/dto/IdeActionDto.java @@ -13,6 +13,7 @@ package org.eclipse.che.api.factory.shared.dto; import static org.eclipse.che.api.core.factory.FactoryParameter.Obligation.OPTIONAL; import org.eclipse.che.api.core.factory.FactoryParameter; +import org.eclipse.che.api.core.model.factory.Action; import org.eclipse.che.dto.shared.DTO; import java.util.Map; @@ -23,27 +24,30 @@ import java.util.Map; * @author Sergii Kabashniuk */ @DTO -public interface Action { +public interface IdeActionDto extends Action { + /** * Action Id * * @return id of action. */ + @Override @FactoryParameter(obligation = OPTIONAL) String getId(); void setId(String id); - Action withId(String id); + IdeActionDto withId(String id); /*** * * @return Action properties */ + @Override @FactoryParameter(obligation = OPTIONAL) Map getProperties(); void setProperties(Map properties); - Action withProperties(Map properties); + IdeActionDto withProperties(Map properties); } diff --git a/wsmaster/che-core-api-factory-shared/src/main/java/org/eclipse/che/api/factory/shared/dto/Ide.java b/wsmaster/che-core-api-factory-shared/src/main/java/org/eclipse/che/api/factory/shared/dto/IdeDto.java similarity index 66% rename from wsmaster/che-core-api-factory-shared/src/main/java/org/eclipse/che/api/factory/shared/dto/Ide.java rename to wsmaster/che-core-api-factory-shared/src/main/java/org/eclipse/che/api/factory/shared/dto/IdeDto.java index 9436af46db..3cc7ab59dd 100644 --- a/wsmaster/che-core-api-factory-shared/src/main/java/org/eclipse/che/api/factory/shared/dto/Ide.java +++ b/wsmaster/che-core-api-factory-shared/src/main/java/org/eclipse/che/api/factory/shared/dto/IdeDto.java @@ -13,6 +13,7 @@ package org.eclipse.che.api.factory.shared.dto; import static org.eclipse.che.api.core.factory.FactoryParameter.Obligation.OPTIONAL; import org.eclipse.che.api.core.factory.FactoryParameter; +import org.eclipse.che.api.core.model.factory.Ide; import org.eclipse.che.dto.shared.DTO; /** @@ -21,36 +22,39 @@ import org.eclipse.che.dto.shared.DTO; * @author Sergii Kabashniuk */ @DTO -public interface Ide { +public interface IdeDto extends Ide { /** * @return configuration of IDE on application loaded event. */ + @Override @FactoryParameter(obligation = OPTIONAL) - OnAppLoaded getOnAppLoaded(); + OnAppLoadedDto getOnAppLoaded(); - void setOnAppLoaded(OnAppLoaded onAppLoaded); + void setOnAppLoaded(OnAppLoadedDto onAppLoaded); - Ide withOnAppLoaded(OnAppLoaded onAppLoaded); + IdeDto withOnAppLoaded(OnAppLoadedDto onAppLoaded); /** * @return configuration of IDE on application closed event. */ + @Override @FactoryParameter(obligation = OPTIONAL) - OnAppClosed getOnAppClosed(); + OnAppClosedDto getOnAppClosed(); - void setOnAppClosed(OnAppClosed onAppClosed); + void setOnAppClosed(OnAppClosedDto onAppClosed); - Ide withOnAppClosed(OnAppClosed onAppClosed); + IdeDto withOnAppClosed(OnAppClosedDto onAppClosed); /** * @return configuration of IDE on projects loaded event. */ + @Override @FactoryParameter(obligation = OPTIONAL) - OnProjectsLoaded getOnProjectsLoaded(); + OnProjectsLoadedDto getOnProjectsLoaded(); - void setOnProjectsLoaded(OnProjectsLoaded onProjectsLoaded); + void setOnProjectsLoaded(OnProjectsLoadedDto onProjectsLoaded); - Ide withOnProjectsLoaded(OnProjectsLoaded onProjectsLoaded); + IdeDto withOnProjectsLoaded(OnProjectsLoadedDto onProjectsLoaded); } diff --git a/wsmaster/che-core-api-factory-shared/src/main/java/org/eclipse/che/api/factory/shared/dto/OnAppClosed.java b/wsmaster/che-core-api-factory-shared/src/main/java/org/eclipse/che/api/factory/shared/dto/OnAppClosedDto.java similarity index 78% rename from wsmaster/che-core-api-factory-shared/src/main/java/org/eclipse/che/api/factory/shared/dto/OnAppClosed.java rename to wsmaster/che-core-api-factory-shared/src/main/java/org/eclipse/che/api/factory/shared/dto/OnAppClosedDto.java index dad926775a..81f4cddc8f 100644 --- a/wsmaster/che-core-api-factory-shared/src/main/java/org/eclipse/che/api/factory/shared/dto/OnAppClosed.java +++ b/wsmaster/che-core-api-factory-shared/src/main/java/org/eclipse/che/api/factory/shared/dto/OnAppClosedDto.java @@ -11,6 +11,7 @@ package org.eclipse.che.api.factory.shared.dto; import org.eclipse.che.api.core.factory.FactoryParameter; +import org.eclipse.che.api.core.model.factory.OnAppClosed; import org.eclipse.che.dto.shared.DTO; import java.util.List; @@ -23,15 +24,16 @@ import static org.eclipse.che.api.core.factory.FactoryParameter.Obligation.OPTIO * @author Sergii Kabashniuk */ @DTO -public interface OnAppClosed { +public interface OnAppClosedDto extends OnAppClosed { /** * @return actions for current event. */ + @Override @FactoryParameter(obligation = OPTIONAL) - List getActions(); + List getActions(); - void setActions(List actions); + void setActions(List actions); - OnAppClosed withActions(List actions); + OnAppClosedDto withActions(List actions); } diff --git a/wsmaster/che-core-api-factory-shared/src/main/java/org/eclipse/che/api/factory/shared/dto/OnAppLoaded.java b/wsmaster/che-core-api-factory-shared/src/main/java/org/eclipse/che/api/factory/shared/dto/OnAppLoadedDto.java similarity index 78% rename from wsmaster/che-core-api-factory-shared/src/main/java/org/eclipse/che/api/factory/shared/dto/OnAppLoaded.java rename to wsmaster/che-core-api-factory-shared/src/main/java/org/eclipse/che/api/factory/shared/dto/OnAppLoadedDto.java index b2e2de2039..75f8ad371d 100644 --- a/wsmaster/che-core-api-factory-shared/src/main/java/org/eclipse/che/api/factory/shared/dto/OnAppLoaded.java +++ b/wsmaster/che-core-api-factory-shared/src/main/java/org/eclipse/che/api/factory/shared/dto/OnAppLoadedDto.java @@ -11,6 +11,7 @@ package org.eclipse.che.api.factory.shared.dto; import org.eclipse.che.api.core.factory.FactoryParameter; +import org.eclipse.che.api.core.model.factory.OnAppLoaded; import org.eclipse.che.dto.shared.DTO; import java.util.List; @@ -23,14 +24,16 @@ import static org.eclipse.che.api.core.factory.FactoryParameter.Obligation.OPTIO * @author Sergii Kabashniuk */ @DTO -public interface OnAppLoaded { +public interface OnAppLoadedDto extends OnAppLoaded { + /** * @return actions for current event. */ + @Override @FactoryParameter(obligation = OPTIONAL) - List getActions(); + List getActions(); - void setActions(List actions); + void setActions(List actions); - OnAppLoaded withActions(List actions); + OnAppLoadedDto withActions(List actions); } diff --git a/wsmaster/che-core-api-factory-shared/src/main/java/org/eclipse/che/api/factory/shared/dto/OnProjectsLoaded.java b/wsmaster/che-core-api-factory-shared/src/main/java/org/eclipse/che/api/factory/shared/dto/OnProjectsLoadedDto.java similarity index 77% rename from wsmaster/che-core-api-factory-shared/src/main/java/org/eclipse/che/api/factory/shared/dto/OnProjectsLoaded.java rename to wsmaster/che-core-api-factory-shared/src/main/java/org/eclipse/che/api/factory/shared/dto/OnProjectsLoadedDto.java index 305988de99..e073173ca7 100644 --- a/wsmaster/che-core-api-factory-shared/src/main/java/org/eclipse/che/api/factory/shared/dto/OnProjectsLoaded.java +++ b/wsmaster/che-core-api-factory-shared/src/main/java/org/eclipse/che/api/factory/shared/dto/OnProjectsLoadedDto.java @@ -11,6 +11,7 @@ package org.eclipse.che.api.factory.shared.dto; import org.eclipse.che.api.core.factory.FactoryParameter; +import org.eclipse.che.api.core.model.factory.OnProjectsLoaded; import org.eclipse.che.dto.shared.DTO; import java.util.List; @@ -23,14 +24,16 @@ import static org.eclipse.che.api.core.factory.FactoryParameter.Obligation.OPTIO * @author Sergii Kabashniuk */ @DTO -public interface OnProjectsLoaded { +public interface OnProjectsLoadedDto extends OnProjectsLoaded { + /** * @return actions for current event. */ + @Override @FactoryParameter(obligation = OPTIONAL) - List getActions(); + List getActions(); - void setActions(List actions); + void setActions(List actions); - OnProjectsLoaded withActions(List actions); + OnProjectsLoadedDto withActions(List actions); } diff --git a/wsmaster/che-core-api-factory-shared/src/main/java/org/eclipse/che/api/factory/shared/dto/Policies.java b/wsmaster/che-core-api-factory-shared/src/main/java/org/eclipse/che/api/factory/shared/dto/PoliciesDto.java similarity index 82% rename from wsmaster/che-core-api-factory-shared/src/main/java/org/eclipse/che/api/factory/shared/dto/Policies.java rename to wsmaster/che-core-api-factory-shared/src/main/java/org/eclipse/che/api/factory/shared/dto/PoliciesDto.java index d5bc3cbb7d..c6cfb10993 100644 --- a/wsmaster/che-core-api-factory-shared/src/main/java/org/eclipse/che/api/factory/shared/dto/Policies.java +++ b/wsmaster/che-core-api-factory-shared/src/main/java/org/eclipse/che/api/factory/shared/dto/PoliciesDto.java @@ -11,9 +11,11 @@ package org.eclipse.che.api.factory.shared.dto; import org.eclipse.che.api.core.factory.FactoryParameter; +import org.eclipse.che.api.core.model.factory.Policies; import org.eclipse.che.dto.shared.DTO; import static org.eclipse.che.api.core.factory.FactoryParameter.Obligation.OPTIONAL; + /** * Describe restrictions of the factory * @@ -21,55 +23,60 @@ import static org.eclipse.che.api.core.factory.FactoryParameter.Obligation.OPTIO * @author Alexander Garagatyi */ @DTO -public interface Policies { +public interface PoliciesDto extends Policies { /** * Restrict access if referer header doesn't match this field */ // Do not change referer to referrer + @Override @FactoryParameter(obligation = OPTIONAL) String getReferer(); void setReferer(String referer); - Policies withReferer(String referer); + PoliciesDto withReferer(String referer); /** * Restrict access for factories used earlier then author supposes */ + @Override @FactoryParameter(obligation = OPTIONAL) Long getSince(); void setSince(Long since); - Policies withSince(Long since); + PoliciesDto withSince(Long since); /** * Restrict access for factories used later then author supposes */ + @Override @FactoryParameter(obligation = OPTIONAL) Long getUntil(); void setUntil(Long until); - Policies withUntil(Long until); + PoliciesDto withUntil(Long until); /** * Re-open project on factory 2-nd click */ + @Override @FactoryParameter(obligation = OPTIONAL) String getMatch(); void setMatch(String match); - Policies withMatch(String match); + PoliciesDto withMatch(String match); /** * Workspace creation strategy */ + @Override @FactoryParameter(obligation = OPTIONAL) String getCreate(); void setCreate(String create); - Policies withCreate(String create); + PoliciesDto withCreate(String create); } diff --git a/wsmaster/che-core-api-factory/pom.xml b/wsmaster/che-core-api-factory/pom.xml index 611cbaa3c0..85c2e826ae 100644 --- a/wsmaster/che-core-api-factory/pom.xml +++ b/wsmaster/che-core-api-factory/pom.xml @@ -45,6 +45,10 @@ io.swagger swagger-annotations + + javax.annotation + javax.annotation-api + javax.inject javax.inject @@ -65,6 +69,10 @@ org.eclipse.che.core che-core-api-factory-shared + + org.eclipse.che.core + che-core-api-machine + org.eclipse.che.core che-core-api-machine-shared @@ -89,10 +97,19 @@ org.eclipse.che.core che-core-commons-lang + + org.eclipse.che.core + che-core-commons-test + org.slf4j slf4j-api + + com.google.inject.extensions + guice-persist + provided + javax.servlet javax.servlet-api @@ -103,21 +120,46 @@ javax.ws.rs-api provided + + org.eclipse.che.core + che-core-api-jdbc + provided + + + org.eclipse.persistence + javax.persistence + provided + ch.qos.logback logback-classic test + + com.h2database + h2 + test + com.jayway.restassured rest-assured test + + org.eclipse.che.core + che-core-api-jdbc-vendor-h2 + test + org.eclipse.che.core che-core-commons-json test + + org.eclipse.persistence + eclipselink + test + org.everrest everrest-assured @@ -162,4 +204,25 @@ test + + + + + org.apache.maven.plugins + maven-jar-plugin + + + + test-jar + + + + **/spi/tck/*.* + + + + + + + diff --git a/wsmaster/che-core-api-factory/src/main/java/org/eclipse/che/api/factory/server/DtoConverter.java b/wsmaster/che-core-api-factory/src/main/java/org/eclipse/che/api/factory/server/DtoConverter.java new file mode 100644 index 0000000000..36e07c7250 --- /dev/null +++ b/wsmaster/che-core-api-factory/src/main/java/org/eclipse/che/api/factory/server/DtoConverter.java @@ -0,0 +1,131 @@ +/******************************************************************************* + * Copyright (c) 2012-2016 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 + *******************************************************************************/ +package org.eclipse.che.api.factory.server; + +import org.eclipse.che.api.core.model.user.User; +import org.eclipse.che.api.factory.shared.dto.AuthorDto; +import org.eclipse.che.api.factory.shared.dto.ButtonAttributesDto; +import org.eclipse.che.api.factory.shared.dto.ButtonDto; +import org.eclipse.che.api.factory.shared.dto.FactoryDto; +import org.eclipse.che.api.factory.shared.dto.IdeActionDto; +import org.eclipse.che.api.factory.shared.dto.IdeDto; +import org.eclipse.che.api.factory.shared.dto.OnAppClosedDto; +import org.eclipse.che.api.factory.shared.dto.OnAppLoadedDto; +import org.eclipse.che.api.factory.shared.dto.OnProjectsLoadedDto; +import org.eclipse.che.api.factory.shared.dto.PoliciesDto; +import org.eclipse.che.api.core.model.factory.Action; +import org.eclipse.che.api.core.model.factory.Author; +import org.eclipse.che.api.core.model.factory.Button; +import org.eclipse.che.api.core.model.factory.ButtonAttributes; +import org.eclipse.che.api.core.model.factory.Factory; +import org.eclipse.che.api.core.model.factory.Ide; +import org.eclipse.che.api.core.model.factory.OnAppClosed; +import org.eclipse.che.api.core.model.factory.OnAppLoaded; +import org.eclipse.che.api.core.model.factory.OnProjectsLoaded; +import org.eclipse.che.api.core.model.factory.Policies; + +import java.util.List; + +import static java.util.stream.Collectors.toList; +import static org.eclipse.che.dto.server.DtoFactory.newDto; + +/** + * Helps to convert to DTOs related to factory. + * + * @author Anton Korneta + */ +public final class DtoConverter { + + public static FactoryDto asDto(Factory factory, User user) { + final FactoryDto factoryDto = newDto(FactoryDto.class).withId(factory.getId()) + .withName(factory.getName()) + .withV(factory.getV()); + + if (factory.getWorkspace() != null) { + factoryDto.withWorkspace(org.eclipse.che.api.workspace.server.DtoConverter.asDto(factory.getWorkspace())); + } + if (factory.getCreator() != null) { + factoryDto.withCreator(asDto(factory.getCreator(), user)); + } + if (factory.getIde() != null) { + factoryDto.withIde(asDto(factory.getIde())); + } + if (factory.getPolicies() != null) { + factoryDto.withPolicies(asDto(factory.getPolicies())); + } + if (factory.getButton() != null) { + factoryDto.withButton(asDto(factory.getButton())); + } + return factoryDto; + } + + public static IdeDto asDto(Ide ide) { + final IdeDto ideDto = newDto(IdeDto.class); + final OnAppClosed onAppClosed = ide.getOnAppClosed(); + final OnAppLoaded onAppLoaded = ide.getOnAppLoaded(); + final OnProjectsLoaded onProjectsLoaded = ide.getOnProjectsLoaded(); + if (onAppClosed != null) { + ideDto.withOnAppClosed(newDto(OnAppClosedDto.class).withActions(asDto(onAppClosed.getActions()))); + } + if (onAppLoaded != null) { + ideDto.withOnAppLoaded(newDto(OnAppLoadedDto.class).withActions(asDto(onAppLoaded.getActions()))); + } + if (onProjectsLoaded != null) { + ideDto.withOnProjectsLoaded(newDto(OnProjectsLoadedDto.class).withActions(asDto(onProjectsLoaded.getActions()))); + } + return ideDto; + } + + public static AuthorDto asDto(Author author, User user) { + return newDto(AuthorDto.class).withUserId(author.getUserId()) + .withName(user.getName()) + .withEmail(user.getEmail()) + .withCreated(author.getCreated()); + } + + public static IdeActionDto asDto(Action action) { + return newDto(IdeActionDto.class).withId(action.getId()) + .withProperties(action.getProperties()); + } + + public static List asDto(List actions) { + return actions.stream().map(DtoConverter::asDto).collect(toList()); + } + + public static PoliciesDto asDto(Policies policies) { + return newDto(PoliciesDto.class).withCreate(policies.getCreate()) + .withMatch(policies.getMatch()) + .withReferer(policies.getReferer()) + .withSince(policies.getSince()) + .withUntil(policies.getUntil()); + } + + public static ButtonDto asDto(Button button) { + final ButtonDto buttonDto = newDto(ButtonDto.class); + if (button.getAttributes() != null) { + buttonDto.withAttributes(asDto(button.getAttributes())); + } + + if (button.getType() != null) { + buttonDto.withType(button.getType()); + } + return buttonDto; + } + + public static ButtonAttributesDto asDto(ButtonAttributes attributes) { + return newDto(ButtonAttributesDto.class).withColor(attributes.getColor()) + .withCounter(attributes.getCounter()) + .withLogo(attributes.getLogo()) + .withStyle(attributes.getStyle()); + } + + private DtoConverter() {} +} diff --git a/wsmaster/che-core-api-factory/src/main/java/org/eclipse/che/api/factory/server/FactoryAcceptValidator.java b/wsmaster/che-core-api-factory/src/main/java/org/eclipse/che/api/factory/server/FactoryAcceptValidator.java index e062550b3c..6e8f07f91b 100644 --- a/wsmaster/che-core-api-factory/src/main/java/org/eclipse/che/api/factory/server/FactoryAcceptValidator.java +++ b/wsmaster/che-core-api-factory/src/main/java/org/eclipse/che/api/factory/server/FactoryAcceptValidator.java @@ -12,7 +12,7 @@ package org.eclipse.che.api.factory.server; import org.eclipse.che.api.core.BadRequestException; -import org.eclipse.che.api.factory.shared.dto.Factory; +import org.eclipse.che.api.factory.shared.dto.FactoryDto; /** * Interface for validations of factory urls on accept stage. @@ -28,5 +28,5 @@ public interface FactoryAcceptValidator { * @throws BadRequestException * in case if factory is not valid */ - void validateOnAccept(Factory factory) throws BadRequestException; + void validateOnAccept(FactoryDto factory) throws BadRequestException; } diff --git a/wsmaster/che-core-api-factory/src/main/java/org/eclipse/che/api/factory/server/FactoryCreateValidator.java b/wsmaster/che-core-api-factory/src/main/java/org/eclipse/che/api/factory/server/FactoryCreateValidator.java index 4987ae83e7..a7f4265c95 100644 --- a/wsmaster/che-core-api-factory/src/main/java/org/eclipse/che/api/factory/server/FactoryCreateValidator.java +++ b/wsmaster/che-core-api-factory/src/main/java/org/eclipse/che/api/factory/server/FactoryCreateValidator.java @@ -13,7 +13,7 @@ package org.eclipse.che.api.factory.server; import org.eclipse.che.api.core.BadRequestException; import org.eclipse.che.api.core.ForbiddenException; import org.eclipse.che.api.core.ServerException; -import org.eclipse.che.api.factory.shared.dto.Factory; +import org.eclipse.che.api.factory.shared.dto.FactoryDto; /** * Interface for validations of factory creation stage. @@ -35,5 +35,5 @@ public interface FactoryCreateValidator { * @throws ForbiddenException * when user have no access rights for factory creation */ - void validateOnCreate(Factory factory) throws BadRequestException, ServerException, ForbiddenException; + void validateOnCreate(FactoryDto factory) throws BadRequestException, ServerException, ForbiddenException; } diff --git a/wsmaster/che-core-api-factory/src/main/java/org/eclipse/che/api/factory/server/FactoryEditValidator.java b/wsmaster/che-core-api-factory/src/main/java/org/eclipse/che/api/factory/server/FactoryEditValidator.java index 072cfe4e11..366004b0bd 100644 --- a/wsmaster/che-core-api-factory/src/main/java/org/eclipse/che/api/factory/server/FactoryEditValidator.java +++ b/wsmaster/che-core-api-factory/src/main/java/org/eclipse/che/api/factory/server/FactoryEditValidator.java @@ -12,7 +12,7 @@ package org.eclipse.che.api.factory.server; import org.eclipse.che.api.core.ForbiddenException; import org.eclipse.che.api.core.ServerException; -import org.eclipse.che.api.factory.shared.dto.Factory; +import org.eclipse.che.api.core.model.factory.Factory; /** * This validator ensures that a factory can be edited by a user that has the associated rights (author or account owner) diff --git a/wsmaster/che-core-api-factory/src/main/java/org/eclipse/che/api/factory/server/FactoryImage.java b/wsmaster/che-core-api-factory/src/main/java/org/eclipse/che/api/factory/server/FactoryImage.java index 4142da29b6..86350b43c6 100644 --- a/wsmaster/che-core-api-factory/src/main/java/org/eclipse/che/api/factory/server/FactoryImage.java +++ b/wsmaster/che-core-api-factory/src/main/java/org/eclipse/che/api/factory/server/FactoryImage.java @@ -10,23 +10,27 @@ *******************************************************************************/ package org.eclipse.che.api.factory.server; -import org.eclipse.che.api.core.ConflictException; - -import java.io.ByteArrayOutputStream; -import java.io.IOException; -import java.io.InputStream; +import javax.persistence.Basic; +import javax.persistence.Embeddable; import java.util.Arrays; +import java.util.Objects; /** Class to hold image information such as data, name, media type */ +@Embeddable public class FactoryImage { + + @Basic private byte[] imageData; + + @Basic private String mediaType; + + @Basic private String name; - public FactoryImage() { - } + public FactoryImage() {} - public FactoryImage(byte[] data, String mediaType, String name) throws IOException { + public FactoryImage(byte[] data, String mediaType, String name) { setMediaType(mediaType); this.name = name; setImageData(data); @@ -36,7 +40,7 @@ public class FactoryImage { return imageData; } - public void setImageData(byte[] imageData) throws IOException { + public void setImageData(byte[] imageData) { this.imageData = imageData; } @@ -44,7 +48,7 @@ public class FactoryImage { return mediaType; } - public void setMediaType(String mediaType) throws IOException { + public void setMediaType(String mediaType) { if (mediaType != null) { switch (mediaType) { case "image/jpeg": @@ -53,10 +57,10 @@ public class FactoryImage { this.mediaType = mediaType; return; default: - throw new IOException("Image media type '" + mediaType + "' is unsupported."); + throw new IllegalArgumentException("Image media type '" + mediaType + "' is unsupported."); } } - throw new IOException("Image media type 'null' is unsupported."); + throw new IllegalArgumentException("Image media type 'null' is unsupported."); } public String getName() { @@ -72,60 +76,21 @@ public class FactoryImage { } @Override - public boolean equals(Object o) { - if (this == o) return true; - if (!(o instanceof FactoryImage)) return false; - - FactoryImage that = (FactoryImage)o; - - if (!Arrays.equals(imageData, that.imageData)) return false; - if (mediaType != null ? !mediaType.equals(that.mediaType) : that.mediaType != null) return false; - if (name != null ? !name.equals(that.name) : that.name != null) return false; - - return true; + public boolean equals(Object obj) { + if (this == obj) return true; + if (!(obj instanceof FactoryImage)) return false; + final FactoryImage other = (FactoryImage)obj; + return Arrays.equals(imageData, other.imageData) + && Objects.equals(mediaType, other.mediaType) + && Objects.equals(name, other.name); } @Override public int hashCode() { - int result = imageData != null ? Arrays.hashCode(imageData) : 0; - result = 31 * result + (mediaType != null ? mediaType.hashCode() : 0); - result = 31 * result + (name != null ? name.hashCode() : 0); - return result; - } - - /** - * Creates {@code FactoryImage}. - * InputStream should be closed manually. - * - * @param is - * - input stream with image data - * @param mediaType - * - media type of image - * @param name - * - image name - * @return - {@code FactoryImage} if {@code FactoryImage} was created, null if input stream has no content - * @throws org.eclipse.che.api.core.ConflictException - */ - public static FactoryImage createImage(InputStream is, String mediaType, String name) throws ConflictException { - try { - ByteArrayOutputStream baos = new ByteArrayOutputStream(); - byte[] buffer = new byte[1024]; - int read; - while ((read = is.read(buffer, 0, buffer.length)) != -1) { - baos.write(buffer, 0, read); - if (baos.size() > 1024 * 1024) { - throw new ConflictException("Maximum upload size exceeded."); - } - } - - if (baos.size() == 0) { - return new FactoryImage(); - } - baos.flush(); - - return new FactoryImage(baos.toByteArray(), mediaType, name); - } catch (IOException e) { - throw new ConflictException(e.getLocalizedMessage()); - } + int hash = 7; + hash = 31 * hash + Arrays.hashCode(imageData); + hash = 31 * hash + Objects.hashCode(mediaType); + hash = 31 * hash + Objects.hashCode(name); + return hash; } } diff --git a/wsmaster/che-core-api-factory/src/main/java/org/eclipse/che/api/factory/server/FactoryLinksHelper.java b/wsmaster/che-core-api-factory/src/main/java/org/eclipse/che/api/factory/server/FactoryLinksHelper.java new file mode 100644 index 0000000000..085ee40b8d --- /dev/null +++ b/wsmaster/che-core-api-factory/src/main/java/org/eclipse/che/api/factory/server/FactoryLinksHelper.java @@ -0,0 +1,158 @@ +/******************************************************************************* + * Copyright (c) 2012-2016 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 + *******************************************************************************/ +package org.eclipse.che.api.factory.server; + +import com.google.common.base.Strings; +import com.google.common.collect.ImmutableList; + +import org.eclipse.che.api.core.rest.ServiceContext; +import org.eclipse.che.api.core.rest.shared.dto.Link; +import org.eclipse.che.api.factory.shared.dto.FactoryDto; + +import javax.ws.rs.HttpMethod; +import javax.ws.rs.core.UriBuilder; +import java.util.LinkedList; +import java.util.List; +import java.util.Set; + +import static java.util.stream.Collectors.toList; +import static javax.ws.rs.core.MediaType.APPLICATION_JSON; +import static javax.ws.rs.core.MediaType.TEXT_HTML; +import static javax.ws.rs.core.MediaType.TEXT_PLAIN; +import static org.eclipse.che.api.core.util.LinksHelper.createLink; +import static org.eclipse.che.api.factory.shared.Constants.ACCEPTED_REL_ATT; +import static org.eclipse.che.api.factory.shared.Constants.FACTORY_ACCEPTANCE_REL_ATT; +import static org.eclipse.che.api.factory.shared.Constants.IMAGE_REL_ATT; +import static org.eclipse.che.api.factory.shared.Constants.NAMED_FACTORY_ACCEPTANCE_REL_ATT; +import static org.eclipse.che.api.factory.shared.Constants.RETRIEVE_FACTORY_REL_ATT; +import static org.eclipse.che.api.factory.shared.Constants.SNIPPET_REL_ATT; + +/** + * Helper class for creation links. + * + * @author Anton Korneta + */ +public class FactoryLinksHelper { + + private static final List SNIPPET_TYPES = ImmutableList.of("markdown", "url", "html", "iframe"); + + private FactoryLinksHelper() {} + + /** + * Creates factory links and links for retrieving factory images. + * + * @param images + * a set of factory images + * @param serviceContext + * the context to retrieve factory service base URI + * @return list of factory and factory images links + */ + public static List createLinks(FactoryDto factory, + Set images, + ServiceContext serviceContext, + String userName) { + final List links = new LinkedList<>(createLinks(factory, serviceContext, userName)); + final UriBuilder uriBuilder = serviceContext.getServiceUriBuilder(); + final String factoryId = factory.getId(); + + // creation of links to retrieve images + links.addAll(images.stream() + .map(image -> createLink(HttpMethod.GET, + uriBuilder.clone() + .path(FactoryService.class, "getImage") + .queryParam("imgId", image.getName()) + .build(factoryId) + .toString(), + null, + image.getMediaType(), + IMAGE_REL_ATT)) + .collect(toList())); + return links; + } + + /** + * Creates factory links. + * + * @param serviceContext + * the context to retrieve factory service base URI + * @return list of factory links + */ + public static List createLinks(FactoryDto factory, + ServiceContext serviceContext, + String userName) { + final List links = new LinkedList<>(); + final UriBuilder uriBuilder = serviceContext.getServiceUriBuilder(); + final String factoryId = factory.getId(); + if (factoryId != null) { + // creation of link to retrieve factory + links.add(createLink(HttpMethod.GET, + uriBuilder.clone() + .path(FactoryService.class, "getFactory") + .build(factoryId) + .toString(), + null, + APPLICATION_JSON, + RETRIEVE_FACTORY_REL_ATT)); + + // creation of snippet links + links.addAll(SNIPPET_TYPES.stream() + .map(snippet -> createLink(HttpMethod.GET, + uriBuilder.clone() + .path(FactoryService.class, + "getFactorySnippet") + .queryParam("type", snippet) + .build(factoryId) + .toString(), + null, + TEXT_PLAIN, + SNIPPET_REL_ATT + '/' + snippet)) + .collect(toList())); + + // creation of accept factory link + final Link createWorkspace = createLink(HttpMethod.GET, + uriBuilder.clone() + .replacePath("f") + .queryParam("id", factoryId) + .build() + .toString(), + null, + TEXT_HTML, + FACTORY_ACCEPTANCE_REL_ATT); + links.add(createWorkspace); + // creation of links for analytics + links.add(createLink(HttpMethod.GET, + uriBuilder.clone() + .path("analytics") + .path("public-metric/factory_used") + .queryParam("factory", createWorkspace.getHref()) + .toString(), + null, + TEXT_PLAIN, + ACCEPTED_REL_ATT)); + } + + if (!Strings.isNullOrEmpty(factory.getName()) && !Strings.isNullOrEmpty(userName)) { + // creation of accept factory link by name and creator + final Link createWorkspaceFromNamedFactory = createLink(HttpMethod.GET, + uriBuilder.clone() + .replacePath("f") + .queryParam("name", factory.getName()) + .queryParam("user", userName) + .build() + .toString(), + null, + TEXT_HTML, + NAMED_FACTORY_ACCEPTANCE_REL_ATT); + links.add(createWorkspaceFromNamedFactory); + } + return links; + } +} diff --git a/wsmaster/che-core-api-factory/src/main/java/org/eclipse/che/api/factory/server/FactoryManager.java b/wsmaster/che-core-api-factory/src/main/java/org/eclipse/che/api/factory/server/FactoryManager.java new file mode 100644 index 0000000000..1c29a66ba4 --- /dev/null +++ b/wsmaster/che-core-api-factory/src/main/java/org/eclipse/che/api/factory/server/FactoryManager.java @@ -0,0 +1,285 @@ +/******************************************************************************* + * Copyright (c) 2012-2016 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 + *******************************************************************************/ +package org.eclipse.che.api.factory.server; + +import org.eclipse.che.api.core.ConflictException; +import org.eclipse.che.api.core.NotFoundException; +import org.eclipse.che.api.core.ServerException; +import org.eclipse.che.api.core.model.factory.Factory; +import org.eclipse.che.api.factory.server.model.impl.AuthorImpl; +import org.eclipse.che.api.factory.server.model.impl.FactoryImpl; +import org.eclipse.che.api.factory.server.snippet.SnippetGenerator; +import org.eclipse.che.api.factory.server.spi.FactoryDao; +import org.eclipse.che.commons.lang.NameGenerator; +import org.eclipse.che.commons.lang.Pair; + +import javax.inject.Inject; +import javax.inject.Singleton; +import javax.ws.rs.core.UriBuilder; +import java.net.URI; +import java.util.List; +import java.util.Set; +import java.util.stream.Collectors; + +import static com.google.common.base.MoreObjects.firstNonNull; +import static java.util.Objects.requireNonNull; +import static org.eclipse.che.api.factory.shared.Constants.HTML_SNIPPET_TYPE; +import static org.eclipse.che.api.factory.shared.Constants.IFRAME_SNIPPET_TYPE; +import static org.eclipse.che.api.factory.shared.Constants.MARKDOWN_SNIPPET_TYPE; +import static org.eclipse.che.api.factory.shared.Constants.URL_SNIPPET_TYPE; + +/** + * @author Anton Korneta + */ +@Singleton +public class FactoryManager { + + @Inject + private FactoryDao factoryDao; + + /** + * Stores {@link Factory} instance. + * + * @param factory + * instance of factory which would be stored + * @return factory which has been stored + * @throws NullPointerException + * when {@code factory} is null + * @throws ConflictException + * when any conflict occurs (e.g Factory with given name already exists for {@code creator}) + * @throws ServerException + * when any server errors occurs + */ + public Factory saveFactory(Factory factory) throws ConflictException, ServerException { + return saveFactory(factory, null); + } + + /** + * Stores {@link Factory} instance and related set of {@link FactoryImage}. + * + * @param factory + * instance of factory which would be stored + * @param images + * factory images which would be stored + * @return factory which has been stored + * @throws NullPointerException + * when {@code factory} is null + * @throws ConflictException + * when any conflict occurs (e.g Factory with given name already exists for {@code creator}) + * @throws ServerException + * when any server errors occurs + */ + public Factory saveFactory(Factory factory, Set images) throws ConflictException, + ServerException { + requireNonNull(factory); + final FactoryImpl newFactory = new FactoryImpl(factory, images); + newFactory.setId(NameGenerator.generate("factory", 16)); + return factoryDao.create(newFactory); + } + + /** + * Updates factory accordance to the new configuration. + * + *

Note: Updating uses replacement strategy, + * therefore existing factory would be replaced with given update {@code update} + * + * @param update + * factory update + * @return updated factory + * @throws NullPointerException + * when {@code update} is null + * @throws ConflictException + * when any conflict occurs (e.g Factory with given name already exists for {@code creator}) + * @throws NotFoundException + * when factory with given id not found + * @throws ServerException + * when any server error occurs + */ + public Factory updateFactory(Factory update) throws ConflictException, + NotFoundException, + ServerException { + requireNonNull(update); + return updateFactory(update, null); + } + + /** + * Updates factory and its images accordance to the new configuration. + * + *

Note: Updating uses replacement strategy, + * therefore existing factory would be replaced with given update {@code update} + * + * @param update + * factory update + * @return updated factory + * @throws NullPointerException + * when {@code update} is null + * @throws ConflictException + * when any conflict occurs (e.g Factory with given name already exists for {@code creator}) + * @throws NotFoundException + * when factory with given id not found + * @throws ServerException + * when any server error occurs + */ + public Factory updateFactory(Factory update, Set images) throws ConflictException, + NotFoundException, + ServerException { + requireNonNull(update); + final AuthorImpl creator = factoryDao.getById(update.getId()).getCreator(); + return factoryDao.update(FactoryImpl.builder() + .from(new FactoryImpl(update, images)) + .setCreator(new AuthorImpl(creator.getUserId(), creator.getCreated())) + .build()); + } + + /** + * Removes stored {@link Factory} by given id. + * + * @param id + * factory identifier + * @throws NullPointerException + * when {@code id} is null + * @throws ServerException + * when any server errors occurs + */ + public void removeFactory(String id) throws ServerException { + requireNonNull(id); + factoryDao.remove(id); + } + + /** + * Gets factory by given id. + * + * @param id + * factory identifier + * @return factory instance + * @throws NullPointerException + * when {@code id} is null + * @throws NotFoundException + * when factory with given id not found + * @throws ServerException + * when any server errors occurs + */ + public Factory getById(String id) throws NotFoundException, + ServerException { + requireNonNull(id); + return factoryDao.getById(id); + } + + /** + * Gets factory images by given factory and image ids. + * + * @param factoryId + * factory identifier + * @param imageId + * image identifier + * @return factory images or empty set if no image found by given {@code imageId} + * @throws NotFoundException + * when specified factory not found + * @throws ServerException + * when any server errors occurs + */ + public Set getFactoryImages(String factoryId, String imageId) throws NotFoundException, + ServerException { + requireNonNull(factoryId); + requireNonNull(imageId); + return getFactoryImages(factoryId).stream() + .filter(image -> imageId.equals(image.getName())) + .collect(Collectors.toSet()); + } + + /** + * Gets all the factory images. + * + * @param factoryId + * factory identifier + * @return factory images or empty set if no image found for factory + * @throws NotFoundException + * when specified factory not found + * @throws ServerException + * when any server errors occurs + */ + public Set getFactoryImages(String factoryId) throws NotFoundException, + ServerException { + requireNonNull(factoryId); + return factoryDao.getById(factoryId).getImages(); + } + + /** + * Get list of factories which conform specified attributes. + * + * @param maxItems + * max number of items in response + * @param skipCount + * skip items. Must be equals or greater then {@code 0} + * @param attributes + * skip items. Must be equals or greater then {@code 0} + * @return stored data, if specified attributes is correct + * @throws ServerException + * when any server errors occurs + */ + @SuppressWarnings("unchecked") + public > T getByAttribute(int maxItems, + int skipCount, + List> attributes) throws ServerException { + return (T)factoryDao.getByAttribute(maxItems, skipCount, attributes); + } + + /** + * Gets factory snippet by factory id and snippet type. + * If snippet type is not set, "url" type will be used as default. + * + * @param factoryId + * id of factory + * @param snippetType + * type of snippet + * @param baseUri + * URI from which will be created snippet + * @return snippet content or null when snippet type not found. + * @throws NotFoundException + * when factory with specified id doesn't not found + * @throws ServerException + * when any server error occurs during snippet creation + */ + public String getFactorySnippet(String factoryId, + String snippetType, + URI baseUri) throws NotFoundException, + ServerException { + requireNonNull(factoryId); + final String baseUrl = UriBuilder.fromUri(baseUri) + .replacePath("") + .build() + .toString(); + switch (firstNonNull(snippetType, URL_SNIPPET_TYPE)) { + case URL_SNIPPET_TYPE: + return UriBuilder.fromUri(baseUri) + .replacePath("factory") + .queryParam("id", factoryId) + .build() + .toString(); + case HTML_SNIPPET_TYPE: + return SnippetGenerator.generateHtmlSnippet(baseUrl, factoryId); + case IFRAME_SNIPPET_TYPE: + return SnippetGenerator.generateiFrameSnippet(baseUrl, factoryId); + case MARKDOWN_SNIPPET_TYPE: + final Set images = getFactoryImages(factoryId); + final String imageId = (images.size() > 0) ? images.iterator().next().getName() + : null; + try { + return SnippetGenerator.generateMarkdownSnippet(baseUrl, getById(factoryId), imageId); + } catch (IllegalArgumentException e) { + throw new ServerException(e.getLocalizedMessage()); + } + default: + // when the specified type is not supported + return null; + } + } +} diff --git a/wsmaster/che-core-api-factory/src/main/java/org/eclipse/che/api/factory/server/FactoryMessageBodyAdapter.java b/wsmaster/che-core-api-factory/src/main/java/org/eclipse/che/api/factory/server/FactoryMessageBodyAdapter.java index 1732157fbf..43aa3751d0 100644 --- a/wsmaster/che-core-api-factory/src/main/java/org/eclipse/che/api/factory/server/FactoryMessageBodyAdapter.java +++ b/wsmaster/che-core-api-factory/src/main/java/org/eclipse/che/api/factory/server/FactoryMessageBodyAdapter.java @@ -13,8 +13,8 @@ package org.eclipse.che.api.factory.server; import com.google.common.collect.ImmutableSet; import com.google.gson.JsonObject; -import org.eclipse.che.api.factory.shared.dto.Factory; -import org.eclipse.che.api.factory.shared.dto.FactoryV4_0; +import org.eclipse.che.api.core.model.factory.Factory; +import org.eclipse.che.api.factory.shared.dto.FactoryDto; import org.eclipse.che.api.workspace.server.WorkspaceConfigMessageBodyAdapter; import javax.inject.Singleton; @@ -30,7 +30,7 @@ public class FactoryMessageBodyAdapter extends WorkspaceConfigMessageBodyAdapter @Override public Set> getTriggers() { - return ImmutableSet.of(Factory.class, FactoryV4_0.class); + return ImmutableSet.of(Factory.class, FactoryDto.class); } @Override diff --git a/wsmaster/che-core-api-factory/src/main/java/org/eclipse/che/api/factory/server/FactoryParametersResolver.java b/wsmaster/che-core-api-factory/src/main/java/org/eclipse/che/api/factory/server/FactoryParametersResolver.java index 87984b9e9a..384cd1d70f 100644 --- a/wsmaster/che-core-api-factory/src/main/java/org/eclipse/che/api/factory/server/FactoryParametersResolver.java +++ b/wsmaster/che-core-api-factory/src/main/java/org/eclipse/che/api/factory/server/FactoryParametersResolver.java @@ -11,7 +11,7 @@ package org.eclipse.che.api.factory.server; import org.eclipse.che.api.core.BadRequestException; -import org.eclipse.che.api.factory.shared.dto.Factory; +import org.eclipse.che.api.factory.shared.dto.FactoryDto; import javax.validation.constraints.NotNull; import java.util.Map; @@ -40,7 +40,5 @@ public interface FactoryParametersResolver { * @throws BadRequestException * when data are invalid */ - Factory createFactory(@NotNull Map factoryParameters) throws BadRequestException; - - + FactoryDto createFactory(@NotNull Map factoryParameters) throws BadRequestException; } diff --git a/wsmaster/che-core-api-factory/src/main/java/org/eclipse/che/api/factory/server/FactoryService.java b/wsmaster/che-core-api-factory/src/main/java/org/eclipse/che/api/factory/server/FactoryService.java index ae20c544a1..b6887856c7 100644 --- a/wsmaster/che-core-api-factory/src/main/java/org/eclipse/che/api/factory/server/FactoryService.java +++ b/wsmaster/che-core-api-factory/src/main/java/org/eclipse/che/api/factory/server/FactoryService.java @@ -16,22 +16,25 @@ import io.swagger.annotations.ApiParam; import io.swagger.annotations.ApiResponse; import io.swagger.annotations.ApiResponses; +import com.google.common.collect.ImmutableSet; import com.google.gson.JsonSyntaxException; import org.apache.commons.fileupload.FileItem; +import org.eclipse.che.api.core.ApiException; import org.eclipse.che.api.core.BadRequestException; import org.eclipse.che.api.core.ConflictException; import org.eclipse.che.api.core.ForbiddenException; import org.eclipse.che.api.core.NotFoundException; import org.eclipse.che.api.core.ServerException; +import org.eclipse.che.api.core.model.factory.Factory; import org.eclipse.che.api.core.model.project.ProjectConfig; +import org.eclipse.che.api.core.model.user.User; import org.eclipse.che.api.core.rest.Service; -import org.eclipse.che.api.core.rest.shared.dto.Link; import org.eclipse.che.api.factory.server.builder.FactoryBuilder; -import org.eclipse.che.api.factory.server.snippet.SnippetGenerator; -import org.eclipse.che.api.factory.shared.dto.Author; -import org.eclipse.che.api.factory.shared.dto.Factory; -import org.eclipse.che.api.user.server.spi.UserDao; +import org.eclipse.che.api.factory.shared.dto.AuthorDto; +import org.eclipse.che.api.factory.shared.dto.FactoryDto; +import org.eclipse.che.api.user.server.PreferenceManager; +import org.eclipse.che.api.user.server.UserManager; import org.eclipse.che.api.workspace.server.WorkspaceManager; import org.eclipse.che.api.workspace.server.model.impl.ProjectConfigImpl; import org.eclipse.che.api.workspace.server.model.impl.WorkspaceImpl; @@ -39,7 +42,7 @@ import org.eclipse.che.commons.env.EnvironmentContext; import org.eclipse.che.commons.lang.NameGenerator; import org.eclipse.che.commons.lang.Pair; import org.eclipse.che.commons.lang.URLEncodedUtils; -import org.eclipse.che.commons.subject.Subject; +import org.eclipse.che.dto.server.DtoFactory; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -56,29 +59,27 @@ import javax.ws.rs.Produces; import javax.ws.rs.QueryParam; import javax.ws.rs.core.Context; import javax.ws.rs.core.Response; -import javax.ws.rs.core.UriBuilder; import javax.ws.rs.core.UriInfo; +import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.InputStream; -import java.io.UnsupportedEncodingException; -import java.util.Arrays; +import java.util.ArrayList; import java.util.Collections; import java.util.HashSet; import java.util.Iterator; import java.util.List; import java.util.Map; -import java.util.Optional; import java.util.Set; import java.util.function.Predicate; import static com.google.common.base.Strings.isNullOrEmpty; +import static java.lang.Boolean.parseBoolean; import static java.util.stream.Collectors.toList; import static javax.ws.rs.core.HttpHeaders.CONTENT_DISPOSITION; import static javax.ws.rs.core.MediaType.APPLICATION_JSON; import static javax.ws.rs.core.MediaType.MULTIPART_FORM_DATA; import static javax.ws.rs.core.MediaType.TEXT_PLAIN; -import static org.eclipse.che.api.workspace.server.DtoConverter.asDto; -import static org.eclipse.che.dto.server.DtoFactory.newDto; +import static org.eclipse.che.api.factory.server.FactoryLinksHelper.createLinks; /** * Defines Factory REST API. @@ -97,11 +98,6 @@ public class FactoryService extends Service { */ public static final String ERROR_NO_RESOLVER_AVAILABLE = "Cannot build factory with any of the provided parameters."; - /** - * If there is no parameter. - */ - public static final String ERROR_NO_PARAMETERS = "Missing parameters"; - /** * Validate query parameter. If true, factory will be validated */ @@ -112,87 +108,71 @@ public class FactoryService extends Service { */ private final Set factoryParametersResolvers; - private final FactoryStore factoryStore; - private final FactoryEditValidator factoryEditValidator; + private final FactoryManager factoryManager; + private final UserManager userManager; + private final PreferenceManager preferenceManager; + private final FactoryEditValidator editValidator; private final FactoryCreateValidator createValidator; private final FactoryAcceptValidator acceptValidator; - private final LinksHelper linksHelper; private final FactoryBuilder factoryBuilder; private final WorkspaceManager workspaceManager; - private final UserDao userDao; @Inject - public FactoryService(FactoryStore factoryStore, + public FactoryService(FactoryManager factoryManager, + UserManager userManager, + PreferenceManager preferenceManager, FactoryCreateValidator createValidator, FactoryAcceptValidator acceptValidator, - FactoryEditValidator factoryEditValidator, - LinksHelper linksHelper, + FactoryEditValidator editValidator, FactoryBuilder factoryBuilder, WorkspaceManager workspaceManager, - FactoryParametersResolverHolder factoryParametersResolverHolder, - UserDao userDao) { - this.factoryStore = factoryStore; + FactoryParametersResolverHolder factoryParametersResolverHolder) { + this.factoryManager = factoryManager; + this.userManager = userManager; this.createValidator = createValidator; + this.preferenceManager = preferenceManager; this.acceptValidator = acceptValidator; - this.factoryEditValidator = factoryEditValidator; - this.linksHelper = linksHelper; + this.editValidator = editValidator; this.factoryBuilder = factoryBuilder; this.workspaceManager = workspaceManager; this.factoryParametersResolvers = factoryParametersResolverHolder.getFactoryParametersResolvers(); - this.userDao = userDao; } - /** - * Save factory to storage and return stored data. Field 'factory' should contains factory information. - * Fields with images should be named 'image'. Acceptable image size 100x100 pixels. - * - * @param formData - * http request form data - * @param uriInfo - * url context - * @return stored data - * @throws ForbiddenException - * when the user have no access rights for saving the factory - * @throws ConflictException - * when an error occurred during saving the factory - * @throws BadRequestException - * when image content cannot be read or is invalid - * @throws ServerException - * when any server errors occurs - */ @POST @Consumes(MULTIPART_FORM_DATA) @Produces(APPLICATION_JSON) - @ApiOperation(value = "Create a Factory and return data", - notes = "Save factory to storage and return stored data. Field 'factory' should contains factory information.") - @ApiResponses({@ApiResponse(code = 200, message = "OK"), - @ApiResponse(code = 400, message = "Parameters are not valid: missing required parameter(s)"), - @ApiResponse(code = 403, message = "You do not have the permissions to perform a Factory save operation"), - @ApiResponse(code = 409, message = "Parameters are not valid: missing parameters causing conflict"), - @ApiResponse(code = 500, message = "Unable to identify user from context")}) - public Factory saveFactory(Iterator formData, @Context UriInfo uriInfo) - throws ForbiddenException, ConflictException, BadRequestException, ServerException, NotFoundException { + @ApiOperation(value = "Create a new factory based on configuration and factory images", + notes = "The field 'factory' is required") + @ApiResponses({@ApiResponse(code = 200, message = "Factory successfully created"), + @ApiResponse(code = 400, message = "Missed required parameters, parameters are not valid"), + @ApiResponse(code = 403, message = "The user does not have rights to create factory"), + @ApiResponse(code = 409, message = "When factory with given name and creator already exists"), + @ApiResponse(code = 500, message = "Internal server error occurred")}) + public FactoryDto saveFactory(Iterator formData) throws ForbiddenException, + ConflictException, + BadRequestException, + ServerException { try { final Set images = new HashSet<>(); - Factory factory = null; + FactoryDto factory = null; while (formData.hasNext()) { final FileItem item = formData.next(); switch (item.getFieldName()) { case ("factory"): { try (InputStream factoryData = item.getInputStream()) { factory = factoryBuilder.build(factoryData); - } catch (JsonSyntaxException e) { - throw new BadRequestException("You have provided an invalid JSON."); + } catch (JsonSyntaxException ex) { + throw new BadRequestException("Invalid JSON value of the field 'factory' provided"); } break; } case ("image"): { try (InputStream imageData = item.getInputStream()) { - final FactoryImage factoryImage = FactoryImage.createImage(imageData, - item.getContentType(), - NameGenerator.generate(null, 16)); - if (factoryImage.hasContent()) { - images.add(factoryImage); + final FactoryImage image = createImage(imageData, + item.getContentType(), + NameGenerator.generate(null, 16)); + if (image.hasContent()) { + images.add(image); } } break; @@ -201,530 +181,351 @@ public class FactoryService extends Service { //DO NOTHING } } - if (factory == null) { - LOG.warn("No factory information found in 'factory' section of multipart form-data."); - throw new BadRequestException("No factory information found in 'factory' section of multipart/form-data."); - } + requiredNotNull(factory, "factory configuration"); processDefaults(factory); createValidator.validateOnCreate(factory); - final Factory storedFactory = factoryStore.getFactory(factoryStore.saveFactory(factory, images)); - return storedFactory.withLinks(createLinks(storedFactory, images, uriInfo)); - } catch (IOException e) { - LOG.error(e.getLocalizedMessage(), e); - throw new ServerException(e.getLocalizedMessage(), e); + return injectLinks(asDto(factoryManager.saveFactory(factory, images)), images); + } catch (IOException ioEx) { + throw new ServerException(ioEx.getLocalizedMessage(), ioEx); } } - /** - * Save factory to storage and return stored data. - * - * @param factory - * instance of factory which would be stored - * @return decorated the factory instance of which has been stored - * @throws BadRequestException - * when stored the factory is invalid - * @throws ServerException - * when any server errors occurs - * @throws ForbiddenException - * when the user have no access rights for saving the factory - * @throws ConflictException - * when stored the factory is already exist - */ @POST @Consumes(APPLICATION_JSON) @Produces(APPLICATION_JSON) - @ApiOperation(value = "Stores the factory from the configuration", - notes = "Stores the factory without pictures and returns instance of the stored factory with links") - @ApiResponses({@ApiResponse(code = 200, message = "OK"), - @ApiResponse(code = 400, message = "Parameters are not valid: missing required parameter(s)"), - @ApiResponse(code = 403, message = "You do not have the permissions to perform a Factory save operation"), - @ApiResponse(code = 409, message = "Parameters are not valid: missing parameters causing conflict"), - @ApiResponse(code = 500, message = "Internal Server Error")}) - public Factory saveFactory(Factory factory) - throws BadRequestException, ServerException, ForbiddenException, ConflictException, NotFoundException { - if (factory == null) { - throw new BadRequestException("Not null factory required"); - } + @ApiOperation(value = "Create a new factory based on configuration", + notes = "Factory will be created without images") + @ApiResponses({@ApiResponse(code = 200, message = "Factory successfully created"), + @ApiResponse(code = 400, message = "Missed required parameters, parameters are not valid"), + @ApiResponse(code = 403, message = "User does not have rights to create factory"), + @ApiResponse(code = 409, message = "When factory with given name and creator already exists"), + @ApiResponse(code = 500, message = "Internal server error occurred")}) + public FactoryDto saveFactory(FactoryDto factory) throws BadRequestException, + ServerException, + ForbiddenException, + ConflictException { + requiredNotNull(factory, "Factory configuration"); factoryBuilder.checkValid(factory); processDefaults(factory); createValidator.validateOnCreate(factory); - final Factory storedFactory = factoryStore.getFactory(factoryStore.saveFactory(factory, null)); - return storedFactory.withLinks(createLinks(storedFactory, null, uriInfo)); + return injectLinks(asDto(factoryManager.saveFactory(factory)), null); } - /** - * Get factory information from storage by specified id. - * - * @param id - * id of factory - * @param uriInfo - * url context - * @return the factory instance if it's found by id - * @throws NotFoundException - * when the factory with specified id doesn't not found - * @throws ServerException - * when any server errors occurs - * @throws BadRequestException - * when the factory is invalid e.g. is expired - */ @GET @Path("/{id}") @Produces(APPLICATION_JSON) - @ApiOperation(value = "Get factory information by its id", - notes = "Get JSON with factory information. Factory id is passed in a path parameter") - @ApiResponses({@ApiResponse(code = 200, message = "OK"), - @ApiResponse(code = 404, message = "Factory not found"), - @ApiResponse(code = 400, message = "Failed to validate factory e.g. if it expired"), - @ApiResponse(code = 500, message = "Internal server error")}) - public Factory getFactory(@ApiParam(value = "Factory ID") - @PathParam("id") - String id, - @ApiParam(value = "Whether or not to validate values like it is done when accepting a Factory", - allowableValues = "true,false", - defaultValue = "false") - @DefaultValue("false") - @QueryParam("validate") - Boolean validate, - @Context - UriInfo uriInfo) throws NotFoundException, ServerException, BadRequestException { - final Factory factory = factoryStore.getFactory(id); - factory.setLinks(createLinks(factory, factoryStore.getFactoryImages(id, null), uriInfo)); + @ApiOperation(value = "Get factory by its identifier", + notes = "If validate parameter is not specified, retrieved factory wont be validated") + @ApiResponses({@ApiResponse(code = 200, message = "Response contains requested factory entry"), + @ApiResponse(code = 400, message = "Missed required parameters, failed to validate factory"), + @ApiResponse(code = 404, message = "Factory with specified identifier does not exist"), + @ApiResponse(code = 500, message = "Internal server error occurred")}) + public FactoryDto getFactory(@ApiParam(value = "Factory identifier") + @PathParam("id") + String factoryId, + @ApiParam(value = "Whether or not to validate values like it is done when accepting the factory", + allowableValues = "true, false", + defaultValue = "false") + @DefaultValue("false") + @QueryParam("validate") + Boolean validate) throws BadRequestException, + NotFoundException, + ServerException { + final FactoryDto factoryDto = asDto(factoryManager.getById(factoryId)); if (validate) { - acceptValidator.validateOnAccept(factory); + acceptValidator.validateOnAccept(factoryDto); } - return factory; + return injectLinks(factoryDto, factoryManager.getFactoryImages(factoryId)); + } + + @GET + @Path("/find") + @Produces(APPLICATION_JSON) + @ApiOperation(value = "Get factory by attribute, " + + "the attribute must match one of the Factory model fields with type 'String', " + + "e.g. (factory.name, factory.creator.name)", + notes = "If specify more than one value for a single query parameter then will be taken the first one") + @ApiResponses({@ApiResponse(code = 200, message = "Response contains list requested factories"), + @ApiResponse(code = 400, message = "When query does not contain at least one attribute to search for"), + @ApiResponse(code = 500, message = "Internal server error")}) + public List getFactoryByAttribute(@DefaultValue("0") + @QueryParam("skipCount") + Integer skipCount, + @DefaultValue("30") + @QueryParam("maxItems") + Integer maxItems, + @Context + UriInfo uriInfo) throws BadRequestException, + ServerException { + final Set skip = ImmutableSet.of("token", "skipCount", "maxItems"); + final List> query = URLEncodedUtils.parse(uriInfo.getRequestUri()) + .entrySet() + .stream() + .filter(param -> !skip.contains(param.getKey()) + && !param.getValue().isEmpty()) + .map(entry -> Pair.of(entry.getKey(), entry.getValue() + .iterator() + .next())) + .collect(toList()); + checkArgument(!query.isEmpty(), "Query must contain at least one attribute"); + final List factories = new ArrayList<>(); + for (Factory factory : factoryManager.getByAttribute(maxItems, skipCount, query)) { + factories.add(injectLinks(asDto(factory), null)); + } + return factories; } - /** - * Updates specified factory with a new factory content. - * - * @param id - * id of factory - * @param newFactory - * the new data for the factory - * @return updated factory with links - * @throws BadRequestException - * when the factory config is invalid - * @throws NotFoundException - * when the factory with specified id doesn't not found - * @throws ServerException - * when any server error occurs - * @throws ForbiddenException - * when the current user is not granted to edit the factory - * @throws ConflictException - * when not rewritable factory information is present in the new factory - */ @PUT @Path("/{id}") @Consumes(APPLICATION_JSON) @Produces(APPLICATION_JSON) - @ApiOperation(value = "Updates factory information by its id", - notes = "Updates factory based on the factory id which is passed in a path parameter. " + + @ApiOperation(value = "Update factory information by configuration and specified identifier", + notes = "Update factory based on the factory id which is passed in a path parameter. " + "For perform this operation user needs respective rights") - @ApiResponses({@ApiResponse(code = 200, message = "OK"), - @ApiResponse(code = 400, message = "Parameters are not valid: missing required parameter(s)"), - @ApiResponse(code = 403, message = "You do not have the permissions to perform a Factory save operation"), - @ApiResponse(code = 409, message = "The factory information is not updateable"), + @ApiResponses({@ApiResponse(code = 200, message = "Factory successfully updated"), + @ApiResponse(code = 400, message = "Missed required parameters, parameters are not valid"), + @ApiResponse(code = 403, message = "User does not have rights to update factory"), @ApiResponse(code = 404, message = "Factory to update not found"), + @ApiResponse(code = 409, message = "Conflict error occurred during factory update" + + "(e.g. Factory with such name and creator already exists)"), @ApiResponse(code = 500, message = "Internal server error")}) - public Factory updateFactory(@ApiParam(value = "Factory id") - @PathParam("id") - String id, - Factory newFactory) - throws BadRequestException, NotFoundException, ServerException, ForbiddenException, ConflictException { - // forbid null update - if (newFactory == null) { - throw new BadRequestException("The factory information is not updateable"); - } - final Factory existingFactory = factoryStore.getFactory(id); - + public FactoryDto updateFactory(@ApiParam(value = "Factory identifier") + @PathParam("id") + String factoryId, + FactoryDto update) throws BadRequestException, + NotFoundException, + ServerException, + ForbiddenException, + ConflictException { + requiredNotNull(update, "Factory configuration"); + update.setId(factoryId); + final Factory existing = factoryManager.getById(factoryId); // check if the current user has enough access to edit the factory - factoryEditValidator.validate(existingFactory); - - processDefaults(newFactory); - newFactory.getCreator().withCreated(existingFactory.getCreator().getCreated()); - newFactory.setId(existingFactory.getId()); - + editValidator.validate(existing); + factoryBuilder.checkValid(update, true); // validate the new content - factoryBuilder.checkValid(newFactory, true); - createValidator.validateOnCreate(newFactory); - - // access granted, user can update the factory - factoryStore.updateFactory(id, newFactory); - newFactory.setLinks(createLinks(newFactory, factoryStore.getFactoryImages(id, null), uriInfo)); - return newFactory; + createValidator.validateOnCreate(update); + return injectLinks(asDto(factoryManager.updateFactory(update)), + factoryManager.getFactoryImages(factoryId)); } - /** - * Removes factory information from storage by its id. - * - * @param id - * id of factory - * @param uriInfo - * url context - * @throws NotFoundException - * when the factory with specified id doesn't not found - * @throws ServerException - * when any server errors occurs - * @throws ForbiddenException - * when user does not have permission for removal the factory - */ @DELETE @Path("/{id}") - @ApiOperation(value = "Removes factory by its id", + @ApiOperation(value = "Removes factory by its identifier", notes = "Removes factory based on the factory id which is passed in a path parameter. " + "For perform this operation user needs respective rights") - @ApiResponses({@ApiResponse(code = 200, message = "OK"), - @ApiResponse(code = 403, message = "You do not have the permissions to perform a Factory remove operation"), + @ApiResponses({@ApiResponse(code = 200, message = "Factory successfully removed"), + @ApiResponse(code = 403, message = "User not authorized to call this operation"), @ApiResponse(code = 404, message = "Factory not found"), @ApiResponse(code = 500, message = "Internal server error")}) - public void removeFactory(@ApiParam(value = "Factory id") + public void removeFactory(@ApiParam(value = "Factory identifier") @PathParam("id") - String id, - @Context - UriInfo uriInfo) throws NotFoundException, ServerException, ForbiddenException { - final Factory factory = factoryStore.getFactory(id); - - // check if the current user has enough access to edit the factory - factoryEditValidator.validate(factory); - - // if validator didn't fail it means that the access is granted - factoryStore.removeFactory(id); + String id) throws ForbiddenException, + ServerException { + factoryManager.removeFactory(id); } - /** - * Get list of factories which conform specified attributes. - * - * @param maxItems - * max number of items in response - * @param skipCount - * skip items. Must be equals or greater then {@code 0} - * @param uriInfo - * url context - * @return stored data, if specified attributes is correct - * @throws BadRequestException - * when no search attributes passed - */ - @GET - @Path("/find") - @Produces(APPLICATION_JSON) - @ApiOperation(value = "Get Factory by attribute", - notes = "If specify more than one value for a single query parameter then will be taken first one") - @ApiResponses({@ApiResponse(code = 200, message = "OK"), - @ApiResponse(code = 400, message = "No search parameters provided"), - @ApiResponse(code = 500, message = "Internal server error")}) - public List getFactoryByAttribute(@DefaultValue("0") - @QueryParam("skipCount") - Integer skipCount, - @DefaultValue("30") - @QueryParam("maxItems") - Integer maxItems, - @Context - UriInfo uriInfo) throws BadRequestException { - final List skipParams = Arrays.asList("token", "skipCount", "maxItems"); - final List> queryParams = URLEncodedUtils.parse(uriInfo.getRequestUri()).entrySet().stream() - .filter(entry -> !skipParams.contains(entry.getKey()) && - !entry.getValue().isEmpty()) - .map(entry -> Pair.of(entry.getKey(), - entry.getValue().iterator().next())) - .collect(toList()); - if (queryParams.isEmpty()) { - throw new BadRequestException("Query must contain at least one attribute."); - } - - return factoryStore.findByAttribute(maxItems, skipCount, queryParams); - } - - /** - * Get image information by its id from specified factory. - * - * @param id - * id of factory - * @param imageId - * image id - * @return image information if ids are correct. If imageId is not set, random image of the factory will be returned, - * if factory has no images, exception will be thrown - * @throws NotFoundException - * when the factory with specified id doesn't not found - * @throws NotFoundException - * when image id is not specified and there is no default image for the specified factory - * @throws NotFoundException - * when image with specified id doesn't exist - */ @GET @Path("/{id}/image") @Produces("image/*") - @ApiOperation(value = "Get factory image information", - notes = "If the factory does not have image with specified id then first found image will be returned") - @ApiResponses({@ApiResponse(code = 200, message = "OK"), - @ApiResponse(code = 404, message = "Factory or image id not found")}) - public Response getImage(@ApiParam(value = "Factory id") + @ApiOperation(value = "Get factory image", + notes = "If image identifier is not specified then first found image will be returned") + @ApiResponses({@ApiResponse(code = 200, message = "Response contains requested factory image"), + @ApiResponse(code = 400, message = "Missed required parameters, parameters are not valid"), + @ApiResponse(code = 404, message = "Factory or factory image not found"), + @ApiResponse(code = 500, message = "Internal server error")}) + public Response getImage(@ApiParam(value = "Factory identifier") @PathParam("id") - String id, - @ApiParam(value = "Image id", required = true) + String factoryId, + @ApiParam(value = "Image identifier") @QueryParam("imgId") - String imageId) throws NotFoundException { - final Set factoryImages = factoryStore.getFactoryImages(id, null); + String imageId) throws NotFoundException, + BadRequestException, + ServerException { + final Set images; if (isNullOrEmpty(imageId)) { - if (factoryImages.isEmpty()) { - LOG.warn("Default image for factory {} is not found.", id); - throw new NotFoundException("Default image for factory " + id + " is not found."); + if ((images = factoryManager.getFactoryImages(factoryId)).isEmpty()) { + LOG.warn("Default image for factory {} is not found.", factoryId); + throw new NotFoundException("Default image for factory " + factoryId + " is not found."); } - final FactoryImage image = factoryImages.iterator().next(); - return Response.ok(image.getImageData(), image.getMediaType()).build(); - } - for (FactoryImage image : factoryImages) { - if (imageId.equals(image.getName())) { - return Response.ok(image.getImageData(), image.getMediaType()).build(); + } else { + if ((images = factoryManager.getFactoryImages(factoryId, imageId)).isEmpty()) { + LOG.warn("Image with id {} is not found.", imageId); + throw new NotFoundException("Image with id " + imageId + " is not found."); } } - LOG.warn("Image with id {} is not found.", imageId); - throw new NotFoundException("Image with id " + imageId + " is not found."); + final FactoryImage image = images.iterator().next(); + return Response.ok(image.getImageData(), image.getMediaType()).build(); } - /** - * Get factory snippet by factory id and snippet type. If snippet type is not set, "url" type will be used as default. - * - * @param id - * id of factory - * @param type - * type of snippet - * @param uriInfo - * url context - * @return snippet content. - * @throws NotFoundException - * when factory with specified id doesn't not found - with response code 400 - * @throws ServerException - * when any server error occurs during snippet creation - * @throws BadRequestException - * when the snippet type is not supported, - * or if the specified factory does not contain enough information for snippet creation - */ @GET @Path("/{id}/snippet") @Produces(TEXT_PLAIN) - @ApiOperation(value = "Get factory snippet by id", - notes = "If snippet type not set then default 'url' will be used") - @ApiResponses({@ApiResponse(code = 200, message = "OK"), - @ApiResponse(code = 400, message = "Parameters are not valid: missing required parameter(s)"), - @ApiResponse(code = 404, message = "Factory or factory images not found"), + @ApiOperation(value = "Get factory snippet", + notes = "If snippet type is not specified then default 'url' will be used") + @ApiResponses({@ApiResponse(code = 200, message = "Response contains requested factory snippet"), + @ApiResponse(code = 400, message = "Missed required parameters, parameters are not valid"), + @ApiResponse(code = 404, message = "Factory or factory snippet not found"), @ApiResponse(code = 500, message = "Internal server error")}) - public String getFactorySnippet(@ApiParam(value = "Factory ID") + public String getFactorySnippet(@ApiParam(value = "Factory identifier") @PathParam("id") - String id, + String factoryId, @ApiParam(value = "Snippet type", required = true, - allowableValues = "url,html,iframe,markdown", + allowableValues = "url, html, iframe, markdown", defaultValue = "url") @DefaultValue("url") @QueryParam("type") - String type, - @Context - UriInfo uriInfo) throws NotFoundException, ServerException, BadRequestException { - final Factory factory = factoryStore.getFactory(id); - final String baseUrl = UriBuilder.fromUri(uriInfo.getBaseUri()).replacePath("").build().toString(); - switch (type) { - case "url": - return UriBuilder.fromUri(uriInfo.getBaseUri()).replacePath("factory").queryParam("id", id).build().toString(); - case "html": - return SnippetGenerator.generateHtmlSnippet(baseUrl, id); - case "iframe": - return SnippetGenerator.generateiFrameSnippet(baseUrl, id); - case "markdown": - final Set factoryImages = factoryStore.getFactoryImages(id, null); - final String imageId = (factoryImages.size() > 0) ? factoryImages.iterator().next().getName() : null; - try { - return SnippetGenerator.generateMarkdownSnippet(baseUrl, factory, imageId); - } catch (IllegalArgumentException e) { - throw new BadRequestException(e.getLocalizedMessage()); - } - default: - LOG.warn("Snippet type {} is unsupported", type); - throw new BadRequestException("Snippet type \"" + type + "\" is unsupported."); - } + String type) throws NotFoundException, + BadRequestException, + ServerException { + final String factorySnippet = factoryManager.getFactorySnippet(factoryId, type, uriInfo.getBaseUri()); + checkArgument(factorySnippet != null, "Snippet type \"" + type + "\" is unsupported."); + return factorySnippet; } - /** - * Generate factory containing workspace configuration. - * Only projects that have {@code SourceStorage} configured can be included. - * - * @param workspace - * workspace id to generate factory from - * @param path - * optional project path, if set, only this project will be included into result projects set - * @throws ServerException - * when any server error occurs during factory getting - * @throws BadRequestException - * when it is impossible get factory from specified workspace e.g. no projects in workspace - * @throws NotFoundException - * when user's workspace with specified id not found - * @throws ForbiddenException - * when user have no access rights e.g. user is not owner of specified workspace - */ @GET @Path("/workspace/{ws-id}") @Produces(APPLICATION_JSON) @ApiOperation(value = "Construct factory from workspace", - notes = "This call returns a Factory.json that is used to create a factory.") - @ApiResponses({@ApiResponse(code = 200, message = "OK"), - @ApiResponse(code = 400, message = "Parameters are not valid: missing required parameter(s)"), - @ApiResponse(code = 403, message = "Access to workspace denied"), + notes = "This call returns a Factory.json that is used to create a factory") + @ApiResponses({@ApiResponse(code = 200, message = "Response contains requested factory JSON"), + @ApiResponse(code = 400, message = "Missed required parameters, parameters are not valid"), @ApiResponse(code = 404, message = "Workspace not found"), @ApiResponse(code = 500, message = "Internal server error")}) - public Response getFactoryJson(@ApiParam(value = "Workspace ID") + public Response getFactoryJson(@ApiParam(value = "Workspace identifier") @PathParam("ws-id") - String workspace, + String wsId, @ApiParam(value = "Project path") @QueryParam("path") - String path) - throws ServerException, BadRequestException, NotFoundException, ForbiddenException { - final WorkspaceImpl usersWorkspace = workspaceManager.getWorkspace(workspace); - excludeProjectsWithoutLocation(usersWorkspace, path); - final Factory factory = newDto(Factory.class).withWorkspace(asDto(usersWorkspace.getConfig())).withV("4.0"); - return Response.ok(factory, APPLICATION_JSON) + String path) throws BadRequestException, + NotFoundException, + ServerException { + final WorkspaceImpl workspace = workspaceManager.getWorkspace(wsId); + excludeProjectsWithoutLocation(workspace, path); + final FactoryDto factoryDto = DtoFactory.newDto(FactoryDto.class) + .withV("4.0") + .withWorkspace(org.eclipse.che.api.workspace.server.DtoConverter + .asDto(workspace.getConfig())); + return Response.ok(factoryDto, APPLICATION_JSON) .header(CONTENT_DISPOSITION, "attachment; filename=factory.json") .build(); } - - /** - * Resolve parameters and build a factory for the given parameters - * - * @param parameters - * map of key/values used to build factory. - * @param uriInfo - * url context - * @return a factory instance if found a matching resolver - * @throws NotFoundException - * when no resolver can be used - * @throws ServerException - * when any server errors occurs - * @throws BadRequestException - * when the factory is invalid e.g. is expired - */ @POST @Path("/resolver") @Consumes(APPLICATION_JSON) @Produces(APPLICATION_JSON) @ApiOperation(value = "Create factory by providing map of parameters", - notes = "Get JSON with factory information.") - @ApiResponses({@ApiResponse(code = 200, message = "OK"), - @ApiResponse(code = 400, message = "Failed to validate factory"), + notes = "Get JSON with factory information") + @ApiResponses({@ApiResponse(code = 200, message = "Factory successfully built from parameters"), + @ApiResponse(code = 400, message = "Missed required parameters, failed to validate factory"), @ApiResponse(code = 500, message = "Internal server error")}) - public Factory resolveFactory( - @ApiParam(value = "Parameters provided to create factories") - final Map parameters, - @ApiParam(value = "Whether or not to validate values like it is done when accepting a Factory", - allowableValues = "true,false", - defaultValue = "false") - @DefaultValue("false") - @QueryParam(VALIDATE_QUERY_PARAMETER) - final Boolean validate, - @Context - final UriInfo uriInfo) throws NotFoundException, ServerException, BadRequestException { + public FactoryDto resolveFactory(@ApiParam(value = "Parameters provided to create factories") + Map parameters, + @ApiParam(value = "Whether or not to validate values like it is done when accepting a Factory", + allowableValues = "true,false", + defaultValue = "false") + @DefaultValue("false") + @QueryParam(VALIDATE_QUERY_PARAMETER) + Boolean validate) throws ServerException, + BadRequestException { - // Check parameter - if (parameters == null) { - throw new BadRequestException(ERROR_NO_PARAMETERS); - } + // check parameter + requiredNotNull(parameters, "Factory build parameters"); - // search matching resolver - Optional factoryParametersResolverOptional = this.factoryParametersResolvers.stream().filter((resolver -> resolver.accept(parameters))).findFirst(); - - // no match - if (!factoryParametersResolverOptional.isPresent()) { - throw new NotFoundException(ERROR_NO_RESOLVER_AVAILABLE); - } - - // create factory from matching resolver - final Factory factory = factoryParametersResolverOptional.get().createFactory(parameters); - - // Apply links - try { - factory.setLinks(linksHelper.createLinks(factory, uriInfo, null)); - } catch (UnsupportedEncodingException e) { - throw new ServerException(e.getLocalizedMessage(), e); - } - - // time to validate the factory - if (validate) { - acceptValidator.validateOnAccept(factory); - } - - return factory; - } - - /** - * Creates factory links. - * - * If factory is named it will be generated accept named link, - * if images set is not null and not empty it will be generate links for them - */ - private List createLinks(Factory factory, Set images, UriInfo uriInfo) throws NotFoundException, ServerException { - try { - String username = null; - if (!isNullOrEmpty(factory.getName())) { - username = userDao.getById(factory.getCreator().getUserId()).getName(); + // search matching resolver and create factory from matching resolver + for (FactoryParametersResolver resolver : factoryParametersResolvers) { + if (resolver.accept(parameters)) { + final FactoryDto factory = resolver.createFactory(parameters); + if (validate) { + acceptValidator.validateOnAccept(factory); + } + return injectLinks(factory, null); } - return images != null && !images.isEmpty() - ? linksHelper.createLinks(factory, images, uriInfo, username) - : linksHelper.createLinks(factory, uriInfo, username); - - } catch (UnsupportedEncodingException e) { - throw new ServerException(e.getLocalizedMessage(), e); } + // no match + throw new BadRequestException(ERROR_NO_RESOLVER_AVAILABLE); } /** - * Filters workspace projects, removes projects which don't have location set. - * If all workspace projects don't have location throws {@link BadRequestException}. + * Injects factory links. If factory is named then accept named link will be injected, + * if {@code images} is not null and not empty then image links will be injected */ - private void excludeProjectsWithoutLocation(WorkspaceImpl usersWorkspace, String projectPath) throws BadRequestException { + private FactoryDto injectLinks(FactoryDto factory, Set images) { + String username = null; + if (factory.getCreator() != null && factory.getCreator().getUserId() != null) { + try { + username = userManager.getById(factory.getCreator().getUserId()).getName(); + } catch (ApiException ignored) { + // when impossible to get username then named factory link won't be injected + } + } + return factory.withLinks(images != null && !images.isEmpty() + ? createLinks(factory, images, getServiceContext(), username) + : createLinks(factory, getServiceContext(), username)); + } + + /** + * Filters workspace projects and removes projects without source location. + * If there is no at least one project with source location then {@link BadRequestException} will be thrown + */ + private static void excludeProjectsWithoutLocation(WorkspaceImpl usersWorkspace, String projectPath) throws BadRequestException { final boolean notEmptyPath = projectPath != null; //Condition for sifting valid project in user's workspace Predicate predicate = projectConfig -> { - // if project is a subproject (it's path contains another project) , then location can be null + // if project is a sub project (it's path contains another project) , then location can be null final boolean isSubProject = projectConfig.getPath().indexOf('/', 1) != -1; final boolean hasNotEmptySource = projectConfig.getSource() != null - && projectConfig.getSource().getType() != null - && projectConfig.getSource().getLocation() != null; + && projectConfig.getSource().getType() != null + && projectConfig.getSource().getLocation() != null; return !(notEmptyPath && !projectPath.equals(projectConfig.getPath())) - && (isSubProject ? true : hasNotEmptySource); + && (isSubProject || hasNotEmptySource); }; - //Filtered out projects by path and source storage presence. + // Filtered out projects by path and source storage presence final List filtered = usersWorkspace.getConfig() .getProjects() .stream() .filter(predicate) .collect(toList()); - if (filtered.isEmpty()) { - throw new BadRequestException("Unable to create factory from this workspace, " + - "because it does not contains projects with source storage set and/or specified path"); - } + checkArgument(!filtered.isEmpty(), "Unable to create factory from this workspace, " + + "because it does not contains projects with source storage"); usersWorkspace.getConfig().setProjects(filtered); } /** - * Adds to the factory information about creator and time of creation + * Checks the current user if it is not temporary then + * adds to the factory creator information and time of creation */ - private void processDefaults(Factory factory) { - final Subject currentSubject = EnvironmentContext.getCurrent().getSubject(); - final Author creator = factory.getCreator(); - if (creator == null) { - factory.setCreator(newDto(Author.class).withUserId(currentSubject.getUserId()) - .withCreated(System.currentTimeMillis())); - return; - } - if (isNullOrEmpty(creator.getUserId())) { - creator.setUserId(currentSubject.getUserId()); - } - if (creator.getCreated() == null) { - creator.setCreated(System.currentTimeMillis()); + private void processDefaults(FactoryDto factory) throws ForbiddenException { + try { + final String userId = EnvironmentContext.getCurrent().getSubject().getUserId(); + final User user = userManager.getById(userId); + if (user == null || parseBoolean(preferenceManager.find(userId).get("temporary"))) { + throw new ForbiddenException("Current user is not allowed to use this method."); + } + factory.setCreator(DtoFactory.newDto(AuthorDto.class) + .withUserId(userId) + .withName(user.getName()) + .withEmail(user.getEmail()) + .withCreated(System.currentTimeMillis())); + } catch (NotFoundException | ServerException ex) { + throw new ForbiddenException("Current user is not allowed to use this method"); } } + /** + * Converts {@link Factory} to dto object + */ + private FactoryDto asDto(Factory factory) throws ServerException { + try { + return DtoConverter.asDto(factory, userManager.getById(factory.getCreator().getUserId())); + } catch (ServerException | NotFoundException ex) { + throw new ServerException("Failed to retrieve factory creator"); + } + } /** * Usage of a dedicated class to manage the optional resolvers @@ -739,6 +540,7 @@ public class FactoryService extends Service { /** * Provides the set of resolvers if there are some else return an empty set. + * * @return a non null set */ public Set getFactoryParametersResolvers() { @@ -748,6 +550,72 @@ public class FactoryService extends Service { return Collections.emptySet(); } } + } + /** + * Creates factory image from input stream. + * InputStream should be closed manually. + * + * @param is + * input stream with image data + * @param mediaType + * media type of image + * @param name + * image name + * @return factory image, if {@param is} has no content then empty factory image will be returned + * @throws BadRequestException + * when factory image exceeded maximum size + * @throws ServerException + * when any server errors occurs + */ + public static FactoryImage createImage(InputStream is, String mediaType, String name) throws BadRequestException, + ServerException { + try { + final ByteArrayOutputStream out = new ByteArrayOutputStream(); + final byte[] buffer = new byte[1024]; + int read; + while ((read = is.read(buffer, 0, buffer.length)) != -1) { + out.write(buffer, 0, read); + if (out.size() > 1024 * 1024) { + throw new BadRequestException("Maximum upload size exceeded."); + } + } + + if (out.size() == 0) { + return new FactoryImage(); + } + out.flush(); + + return new FactoryImage(out.toByteArray(), mediaType, name); + } catch (IOException ioEx) { + throw new ServerException(ioEx.getLocalizedMessage()); + } + } + + /** + * Checks object reference is not {@code null} + * + * @param object + * object reference to check + * @param subject + * used as subject of exception message "{subject} required" + * @throws BadRequestException + * when object reference is {@code null} + */ + private static void requiredNotNull(Object object, String subject) throws BadRequestException { + if (object == null) { + throw new BadRequestException(subject + " required"); + } + } + + /** + * Checks that expression is true, throws {@link BadRequestException} otherwise. + * + *

Exception uses error message built from error message template and error message parameters. + */ + private static void checkArgument(boolean expression, String errorMessage) throws BadRequestException { + if (!expression) { + throw new BadRequestException(errorMessage); + } } } diff --git a/wsmaster/che-core-api-factory/src/main/java/org/eclipse/che/api/factory/server/FactoryStore.java b/wsmaster/che-core-api-factory/src/main/java/org/eclipse/che/api/factory/server/FactoryStore.java deleted file mode 100644 index ee0b503e8f..0000000000 --- a/wsmaster/che-core-api-factory/src/main/java/org/eclipse/che/api/factory/server/FactoryStore.java +++ /dev/null @@ -1,125 +0,0 @@ -/******************************************************************************* - * Copyright (c) 2012-2016 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 - *******************************************************************************/ -package org.eclipse.che.api.factory.server; - -import org.eclipse.che.api.core.ConflictException; -import org.eclipse.che.api.core.NotFoundException; -import org.eclipse.che.api.core.ServerException; -import org.eclipse.che.api.factory.shared.dto.Factory; -import org.eclipse.che.commons.lang.Pair; - -import java.util.List; -import java.util.Set; - -/** - * Interface for CRUD operations with factory data. - * - * @author Max Shaposhnik - */ - -public interface FactoryStore { - /** - * Save factory at storage. - * - * @param factory - * factory information - * @param images - * factory images - * @return id of stored factory - * @throws java.lang.RuntimeException - * if {@code factory} is null - * @throws org.eclipse.che.api.core.ConflictException - * if {@code factory} with given name and creator already exists - * @throws org.eclipse.che.api.core.ServerException - * if other error occurs - */ - public String saveFactory(Factory factory, Set images) throws ConflictException, ServerException; - - /** - * Remove factory by id - * - * @param factoryId - * - id of factory to remove - * @throws org.eclipse.che.api.core.NotFoundException - * if factory with given {@code factoryId} is not found - * @throws java.lang.RuntimeException - * if {@code factoryId} is null - * @throws org.eclipse.che.api.core.ServerException - * if other error occurs - */ - public void removeFactory(String factoryId) throws NotFoundException, ServerException; - - /** - * Retrieve factory data by its id - * - * @param factoryId - * - factory id - * @return - {@code AdvancedFactoryUrl} if factory exist and found - * @throws org.eclipse.che.api.core.NotFoundException - * if factory with given {@code factoryId} is not found - * @throws java.lang.RuntimeException - * if {@code factoryId} is null - * @throws org.eclipse.che.api.core.ServerException - * if other error occurs - * - */ - public Factory getFactory(String factoryId) throws NotFoundException, ServerException; - - /** - * Retrieve factory by given list of pairs of attribute names and values. - * - * @param maxItems - * max number of items in response. - * @param skipCount - * skip items. Must be equals or greater then {@code 0}. IllegalArgumentException thrown otherwise. - * @param attributes - * attribute pairs to search for - * - * @return - List {@code AdvancedFactoryUrl} if factory(s) exist and found, empty list otherwise - * @throws org.eclipse.che.api.core.IllegalArgumentException - * if {@code skipCount} is negative - * - */ - public List findByAttribute(int maxItems, int skipCount, List> attributes) throws IllegalArgumentException; - - /** - * Retrieve factory images by factory id - * - * @param factoryId - * factory id. Must not be null. - * @param imageId - * id of the requested image. When null, all images for given factory will be returned. - * @return {@code Set} of images if factory found, empty set otherwise - * @throws java.lang.RuntimeException - * if {@code factoryId} is null - * @throws org.eclipse.che.api.core.NotFoundException - * if factory with given {@code factoryId} is not found - */ - public Set getFactoryImages(String factoryId, String imageId) throws NotFoundException; - - /** - * Update factory at storage. - * - * @param factoryId - * factory id to update. Must not be null. - * @param factory - * factory information. Must not be null. - * @return id of stored factory - * @throws org.eclipse.che.api.core.NotFoundException - * if factory with given {@code factoryId} is not found - * @throws org.eclipse.che.api.core.ConflictException - * if {@code factory} with given name and creator already exists - * @throws java.lang.RuntimeException - * if {@code factory} is null - */ - public String updateFactory(String factoryId, Factory factory) throws NotFoundException, ConflictException; - -} diff --git a/wsmaster/che-core-api-factory/src/main/java/org/eclipse/che/api/factory/server/LegacyConverter.java b/wsmaster/che-core-api-factory/src/main/java/org/eclipse/che/api/factory/server/LegacyConverter.java index e4733c75b4..4b3ecc4460 100644 --- a/wsmaster/che-core-api-factory/src/main/java/org/eclipse/che/api/factory/server/LegacyConverter.java +++ b/wsmaster/che-core-api-factory/src/main/java/org/eclipse/che/api/factory/server/LegacyConverter.java @@ -11,7 +11,7 @@ package org.eclipse.che.api.factory.server; import org.eclipse.che.api.core.ApiException; -import org.eclipse.che.api.factory.shared.dto.Factory; +import org.eclipse.che.api.core.model.factory.Factory; /** * Convert legacy factory parameter to new the latest format diff --git a/wsmaster/che-core-api-factory/src/main/java/org/eclipse/che/api/factory/server/LinksHelper.java b/wsmaster/che-core-api-factory/src/main/java/org/eclipse/che/api/factory/server/LinksHelper.java deleted file mode 100644 index 66a2b50cfd..0000000000 --- a/wsmaster/che-core-api-factory/src/main/java/org/eclipse/che/api/factory/server/LinksHelper.java +++ /dev/null @@ -1,200 +0,0 @@ -/******************************************************************************* - * Copyright (c) 2012-2016 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 - *******************************************************************************/ -package org.eclipse.che.api.factory.server; - -import com.google.common.base.Strings; -import org.eclipse.che.api.core.rest.shared.dto.Link; -import org.eclipse.che.api.core.rest.shared.dto.LinkParameter; -import org.eclipse.che.api.factory.shared.dto.Factory; -import org.eclipse.che.dto.server.DtoFactory; - -import javax.inject.Singleton; -import javax.ws.rs.HttpMethod; -import javax.ws.rs.core.MediaType; -import javax.ws.rs.core.UriBuilder; -import javax.ws.rs.core.UriInfo; -import java.io.UnsupportedEncodingException; -import java.net.URLEncoder; -import java.util.Arrays; -import java.util.Collections; -import java.util.LinkedList; -import java.util.List; -import java.util.Set; - -import static java.util.stream.Collectors.toCollection; -import static java.util.stream.Collectors.toList; - -/** Helper class for creation links. */ -@Singleton -public class LinksHelper { - - private static final String IMAGE_REL_ATT = "image"; - private static final String RETRIEVE_FACTORY_REL_ATT = "self"; - private static final String SNIPPET_REL_ATT = "snippet/"; - private static final String FACTORY_ACCEPTANCE_REL_ATT = "accept"; - private static final String NAMED_FACTORY_ACCEPTANCE_REL_ATT = "accept-named"; - private static final String ACCEPTED_REL_ATT = "accepted"; - - private static List snippetTypes = Collections.unmodifiableList(Arrays.asList("markdown", "url", "html", "iframe")); - - /** - * Creates factory links and links on factory images. - * - * @param images - * a set of factory images - * @param uriInfo - * URI information about relative URIs are relative to the base URI - * @return list of factory links - * @throws UnsupportedEncodingException - * occurs when impossible to encode URL - */ - public List createLinks(Factory factory, Set images, UriInfo uriInfo, String userName) throws UnsupportedEncodingException { - final List links = new LinkedList<>(createLinks(factory, uriInfo, userName)); - final UriBuilder baseUriBuilder = uriInfo != null ? UriBuilder.fromUri(uriInfo.getBaseUri()) : UriBuilder.fromUri("/"); - - // add path to factory service - final UriBuilder factoryUriBuilder = baseUriBuilder.clone().path(FactoryService.class); - final String factoryId = factory.getId(); - - // uri's to retrieve images - links.addAll(images.stream() - .map(image -> createLink(HttpMethod.GET, - IMAGE_REL_ATT, - null, - image.getMediaType(), - factoryUriBuilder.clone() - .path(FactoryService.class, "getImage") - .queryParam("imgId", image.getName()) - .build(factoryId) - .toString())) - .collect(toList())); - return links; - } - - /** - * Creates factory links. - * - * @param uriInfo - * URI information about relative URIs are relative to the base URI - * @return list of factory links - * @throws UnsupportedEncodingException - * occurs when impossible to encode URL - */ - public List createLinks(Factory factory, UriInfo uriInfo, String userName) throws UnsupportedEncodingException { - final List links = new LinkedList<>(); - final UriBuilder baseUriBuilder = uriInfo != null ? UriBuilder.fromUri(uriInfo.getBaseUri()) : UriBuilder.fromUri("/"); - - // add path to factory service - final UriBuilder factoryUriBuilder = baseUriBuilder.clone().path(FactoryService.class); - final String factoryId = factory.getId(); - if (factoryId != null) { - // uri to retrieve factory - links.add(createLink(HttpMethod.GET, - RETRIEVE_FACTORY_REL_ATT, - null, - MediaType.APPLICATION_JSON, - factoryUriBuilder.clone() - .path(FactoryService.class, "getFactory") - .build(factoryId) - .toString())); - - // uri's of snippets - links.addAll(snippetTypes.stream() - .map(snippet -> createLink(HttpMethod.GET, - SNIPPET_REL_ATT + snippet, - null, - MediaType.TEXT_PLAIN, - factoryUriBuilder.clone() - .path(FactoryService.class, "getFactorySnippet") - .queryParam("type", snippet) - .build(factoryId) - .toString())) - .collect(toList())); - - // uri to accept factory - final Link createWorkspace = createLink(HttpMethod.GET, - FACTORY_ACCEPTANCE_REL_ATT, - null, - MediaType.TEXT_HTML, - baseUriBuilder.clone() - .replacePath("f") - .queryParam("id", factoryId) - .build() - .toString()); - links.add(createWorkspace); - - - // links of analytics - links.add(createLink(HttpMethod.GET, - ACCEPTED_REL_ATT, - null, - MediaType.TEXT_PLAIN, - baseUriBuilder.clone() - .path("analytics") - .path("public-metric/factory_used") - .queryParam("factory", URLEncoder.encode(createWorkspace.getHref(), "UTF-8")) - .build() - .toString())); - } - - if (!Strings.isNullOrEmpty(factory.getName()) && !Strings.isNullOrEmpty(userName)) { - // uri to accept factory by name and creator - final Link createWorkspaceFromNamedFactory = createLink(HttpMethod.GET, - NAMED_FACTORY_ACCEPTANCE_REL_ATT, - null, - MediaType.TEXT_HTML, - baseUriBuilder.clone() - .replacePath("f") - .queryParam("name", factory.getName()) - .queryParam("user", userName) - .build() - .toString()); - links.add(createWorkspaceFromNamedFactory); - } - - return links; - } - - /** - * Find links with given relation. - * - * @param links - * links for searching - * @param relation - * searching relation - * @return set of links with relation equal to desired, empty set if there is no such links - */ - public List getLinkByRelation(List links, String relation) { - if (relation == null || links == null) { - throw new IllegalArgumentException("Value of parameters can't be null."); - } - return links.stream() - .filter(link -> relation.equals(link.getRel())) - .collect(toCollection(LinkedList::new)); - } - - /** Creates factory Link */ - private Link createLink(String method, String rel, String consumes, String produces, String href) { - return createLink(method, rel, consumes, produces, href, null); - } - - /** Creates factory Link */ - private Link createLink(String method, String rel, String consumes, String produces, String href, List params) { - return DtoFactory.getInstance() - .createDto(Link.class) - .withMethod(method) - .withRel(rel) - .withProduces(produces) - .withConsumes(consumes) - .withHref(href) - .withParameters(params); - } -} diff --git a/wsmaster/che-core-api-factory/src/main/java/org/eclipse/che/api/factory/server/builder/FactoryBuilder.java b/wsmaster/che-core-api-factory/src/main/java/org/eclipse/che/api/factory/server/builder/FactoryBuilder.java index 127d656c24..89df05876b 100644 --- a/wsmaster/che-core-api-factory/src/main/java/org/eclipse/che/api/factory/server/builder/FactoryBuilder.java +++ b/wsmaster/che-core-api-factory/src/main/java/org/eclipse/che/api/factory/server/builder/FactoryBuilder.java @@ -21,8 +21,7 @@ import org.eclipse.che.api.factory.server.FactoryConstants; import org.eclipse.che.api.factory.server.LegacyConverter; import org.eclipse.che.api.factory.server.ValueHelper; import org.eclipse.che.api.factory.server.impl.SourceStorageParametersValidator; -import org.eclipse.che.api.factory.shared.dto.Factory; -import org.eclipse.che.api.factory.shared.dto.FactoryV4_0; +import org.eclipse.che.api.factory.shared.dto.FactoryDto; import org.eclipse.che.api.workspace.shared.dto.SourceStorageDto; import org.eclipse.che.dto.server.DtoFactory; import org.eclipse.che.dto.shared.DTO; @@ -80,9 +79,9 @@ public class FactoryBuilder { * - json Reader from encoded factory. * @return - Factory object represented by given factory json. */ - public Factory build(Reader json) throws IOException, ApiException { - Factory factory = DtoFactory.getInstance() - .createDtoFromJson(json, Factory.class); + public FactoryDto build(Reader json) throws IOException, ApiException { + FactoryDto factory = DtoFactory.getInstance() + .createDtoFromJson(json, FactoryDto.class); checkValid(factory); return factory; } @@ -94,9 +93,9 @@ public class FactoryBuilder { * - json string from encoded factory. * @return - Factory object represented by given factory json. */ - public Factory build(String json) throws ApiException { - Factory factory = DtoFactory.getInstance() - .createDtoFromJson(json, Factory.class); + public FactoryDto build(String json) throws ApiException { + FactoryDto factory = DtoFactory.getInstance() + .createDtoFromJson(json, FactoryDto.class); checkValid(factory); return factory; } @@ -108,9 +107,9 @@ public class FactoryBuilder { * - json InputStream from encoded factory. * @return - Factory object represented by given factory json. */ - public Factory build(InputStream json) throws IOException, ConflictException { - Factory factory = DtoFactory.getInstance() - .createDtoFromJson(json, Factory.class); + public FactoryDto build(InputStream json) throws IOException, ConflictException { + FactoryDto factory = DtoFactory.getInstance() + .createDtoFromJson(json, FactoryDto.class); checkValid(factory); return factory; } @@ -122,7 +121,7 @@ public class FactoryBuilder { * - factory object to validate * @throws ConflictException */ - public void checkValid(Factory factory) throws ConflictException { + public void checkValid(FactoryDto factory) throws ConflictException { checkValid(factory, false); } @@ -136,7 +135,7 @@ public class FactoryBuilder { * Set-by-server variables are allowed during update. * @throws ConflictException */ - public void checkValid(Factory factory, boolean isUpdate) throws ConflictException { + public void checkValid(FactoryDto factory, boolean isUpdate) throws ConflictException { if (null == factory) { throw new ConflictException(FactoryConstants.UNPARSABLE_FACTORY_MESSAGE); } @@ -154,12 +153,12 @@ public class FactoryBuilder { Class usedFactoryVersionMethodProvider; switch (v) { case V4_0: - usedFactoryVersionMethodProvider = FactoryV4_0.class; + usedFactoryVersionMethodProvider = FactoryDto.class; break; default: throw new ConflictException(FactoryConstants.INVALID_VERSION_MESSAGE); } - validateCompatibility(factory, null, Factory.class, usedFactoryVersionMethodProvider, v, "", isUpdate); + validateCompatibility(factory, null, FactoryDto.class, usedFactoryVersionMethodProvider, v, "", isUpdate); } /** @@ -170,8 +169,8 @@ public class FactoryBuilder { * @return - factory in latest format. * @throws org.eclipse.che.api.core.ApiException */ - public Factory convertToLatest(Factory factory) throws ApiException { - Factory resultFactory = DtoFactory.getInstance().clone(factory).withV("4.0"); + public FactoryDto convertToLatest(FactoryDto factory) throws ApiException { + FactoryDto resultFactory = DtoFactory.getInstance().clone(factory).withV("4.0"); for (LegacyConverter converter : LEGACY_CONVERTERS) { converter.convert(resultFactory); } diff --git a/wsmaster/che-core-api-factory/src/main/java/org/eclipse/che/api/factory/server/impl/FactoryAcceptValidatorImpl.java b/wsmaster/che-core-api-factory/src/main/java/org/eclipse/che/api/factory/server/impl/FactoryAcceptValidatorImpl.java index dacb658c6b..274bb67dd2 100644 --- a/wsmaster/che-core-api-factory/src/main/java/org/eclipse/che/api/factory/server/impl/FactoryAcceptValidatorImpl.java +++ b/wsmaster/che-core-api-factory/src/main/java/org/eclipse/che/api/factory/server/impl/FactoryAcceptValidatorImpl.java @@ -12,7 +12,7 @@ package org.eclipse.che.api.factory.server.impl; import org.eclipse.che.api.core.BadRequestException; import org.eclipse.che.api.factory.server.FactoryAcceptValidator; -import org.eclipse.che.api.factory.shared.dto.Factory; +import org.eclipse.che.api.factory.shared.dto.FactoryDto; import org.eclipse.che.api.user.server.spi.PreferenceDao; import javax.inject.Inject; @@ -23,13 +23,9 @@ import javax.inject.Singleton; */ @Singleton public class FactoryAcceptValidatorImpl extends FactoryBaseValidator implements FactoryAcceptValidator { - @Inject - public FactoryAcceptValidatorImpl(PreferenceDao preferenceDao) { - super(preferenceDao); - } @Override - public void validateOnAccept(Factory factory) throws BadRequestException { + public void validateOnAccept(FactoryDto factory) throws BadRequestException { validateCurrentTimeBetweenSinceUntil(factory); validateProjectActions(factory); } diff --git a/wsmaster/che-core-api-factory/src/main/java/org/eclipse/che/api/factory/server/impl/FactoryBaseValidator.java b/wsmaster/che-core-api-factory/src/main/java/org/eclipse/che/api/factory/server/impl/FactoryBaseValidator.java index 73feb7a8ff..29c4fa28f8 100644 --- a/wsmaster/che-core-api-factory/src/main/java/org/eclipse/che/api/factory/server/impl/FactoryBaseValidator.java +++ b/wsmaster/che-core-api-factory/src/main/java/org/eclipse/che/api/factory/server/impl/FactoryBaseValidator.java @@ -11,16 +11,13 @@ package org.eclipse.che.api.factory.server.impl; import org.eclipse.che.api.core.BadRequestException; -import org.eclipse.che.api.core.ForbiddenException; -import org.eclipse.che.api.core.ServerException; import org.eclipse.che.api.factory.server.FactoryConstants; -import org.eclipse.che.api.factory.shared.dto.Action; -import org.eclipse.che.api.factory.shared.dto.Factory; -import org.eclipse.che.api.factory.shared.dto.Ide; -import org.eclipse.che.api.factory.shared.dto.OnAppLoaded; -import org.eclipse.che.api.factory.shared.dto.OnProjectsLoaded; -import org.eclipse.che.api.factory.shared.dto.Policies; -import org.eclipse.che.api.user.server.spi.PreferenceDao; +import org.eclipse.che.api.factory.shared.dto.FactoryDto; +import org.eclipse.che.api.factory.shared.dto.IdeActionDto; +import org.eclipse.che.api.factory.shared.dto.IdeDto; +import org.eclipse.che.api.factory.shared.dto.OnAppLoadedDto; +import org.eclipse.che.api.factory.shared.dto.OnProjectsLoadedDto; +import org.eclipse.che.api.factory.shared.dto.PoliciesDto; import org.eclipse.che.api.workspace.shared.dto.ProjectConfigDto; import java.io.UnsupportedEncodingException; @@ -31,7 +28,6 @@ import java.util.Map; import java.util.regex.Pattern; import static com.google.common.base.Strings.isNullOrEmpty; -import static java.lang.Boolean.parseBoolean; import static java.lang.String.format; import static java.lang.System.currentTimeMillis; @@ -44,12 +40,6 @@ import static java.lang.System.currentTimeMillis; public abstract class FactoryBaseValidator { private static final Pattern PROJECT_NAME_VALIDATOR = Pattern.compile("^[\\\\\\w\\\\\\d]+[\\\\\\w\\\\\\d_.-]*$"); - private final PreferenceDao preferenceDao; - - public FactoryBaseValidator(PreferenceDao preferenceDao) { - this.preferenceDao = preferenceDao; - } - /** * Validates source parameter of factory. * @@ -58,7 +48,7 @@ public abstract class FactoryBaseValidator { * @throws BadRequestException * when source projects in the factory is invalid */ - protected void validateProjects(Factory factory) throws BadRequestException { + protected void validateProjects(FactoryDto factory) throws BadRequestException { for (ProjectConfigDto project : factory.getWorkspace().getProjects()) { final String projectName = project.getName(); if (null != projectName && !PROJECT_NAME_VALIDATOR.matcher(projectName) @@ -89,31 +79,6 @@ public abstract class FactoryBaseValidator { } } - /** - * Validates that creator of factory is really owner of account specified in it. - * - * @param factory - * factory to validate - * @throws ServerException - * when any server errors occurs - * @throws ForbiddenException - * when user does not have required rights - */ - protected void validateAccountId(Factory factory) throws ServerException, ForbiddenException { - // TODO do we need check if user is temporary? - final String userId = factory.getCreator() != null ? factory.getCreator().getUserId() : null; - - if (userId == null) { - return; - } - - final Map preferences = preferenceDao.getPreferences(userId); - if (parseBoolean(preferences.get("temporary"))) { - throw new ForbiddenException("Current user is not allowed to use this method."); - } - - } - /** * Validates that factory can be used at present time (used on accept) * @@ -124,8 +89,8 @@ public abstract class FactoryBaseValidator { * @throws BadRequestException * if until date less than current date
*/ - protected void validateCurrentTimeBetweenSinceUntil(Factory factory) throws BadRequestException { - final Policies policies = factory.getPolicies(); + protected void validateCurrentTimeBetweenSinceUntil(FactoryDto factory) throws BadRequestException { + final PoliciesDto policies = factory.getPolicies(); if (policies == null) { return; } @@ -148,14 +113,14 @@ public abstract class FactoryBaseValidator { * @param factory * factory to validate * @throws BadRequestException - * if since date greater or equal than until date
+ * if since date greater or equal than until date * @throws BadRequestException - * if since date less than current date
+ * if since date less than current date * @throws BadRequestException - * if until date less than current date
+ * if until date less than current date */ - protected void validateCurrentTimeAfterSinceUntil(Factory factory) throws BadRequestException { - final Policies policies = factory.getPolicies(); + protected void validateCurrentTimeAfterSinceUntil(FactoryDto factory) throws BadRequestException { + final PoliciesDto policies = factory.getPolicies(); if (policies == null) { return; } @@ -185,13 +150,13 @@ public abstract class FactoryBaseValidator { * @throws BadRequestException * when factory actions is invalid */ - protected void validateProjectActions(Factory factory) throws BadRequestException { - final Ide ide = factory.getIde(); + protected void validateProjectActions(FactoryDto factory) throws BadRequestException { + final IdeDto ide = factory.getIde(); if (ide == null) { return; } - final List applicationActions = new ArrayList<>(); + final List applicationActions = new ArrayList<>(); if (ide.getOnAppClosed() != null) { applicationActions.addAll(ide.getOnAppClosed().getActions()); } @@ -199,16 +164,16 @@ public abstract class FactoryBaseValidator { applicationActions.addAll(ide.getOnAppLoaded().getActions()); } - for (Action applicationAction : applicationActions) { + for (IdeActionDto applicationAction : applicationActions) { String id = applicationAction.getId(); if ("openFile".equals(id) || "findReplace".equals(id) || "runCommand".equals(id) || "newTerminal".equals(id)) { throw new BadRequestException(format(FactoryConstants.INVALID_ACTION_SECTION, id)); } } - final OnAppLoaded onAppLoaded = ide.getOnAppLoaded(); + final OnAppLoadedDto onAppLoaded = ide.getOnAppLoaded(); if (onAppLoaded != null) { - for (Action action : onAppLoaded.getActions()) { + for (IdeActionDto action : onAppLoaded.getActions()) { final Map properties = action.getProperties(); if ("openWelcomePage".equals(action.getId()) && isNullOrEmpty(properties.get("greetingContentUrl"))) { throw new BadRequestException(FactoryConstants.INVALID_WELCOME_PAGE_ACTION); @@ -216,10 +181,10 @@ public abstract class FactoryBaseValidator { } } - final OnProjectsLoaded onLoaded = ide.getOnProjectsLoaded(); + final OnProjectsLoadedDto onLoaded = ide.getOnProjectsLoaded(); if (onLoaded != null) { - final List onProjectOpenedActions = onLoaded.getActions(); - for (Action applicationAction : onProjectOpenedActions) { + final List onProjectOpenedActions = onLoaded.getActions(); + for (IdeActionDto applicationAction : onProjectOpenedActions) { final String id = applicationAction.getId(); final Map properties = applicationAction.getProperties(); @@ -229,7 +194,7 @@ public abstract class FactoryBaseValidator { throw new BadRequestException(FactoryConstants.INVALID_OPENFILE_ACTION); } break; - + case "runCommand": if (isNullOrEmpty(properties.get("name"))) { throw new BadRequestException(FactoryConstants.INVALID_RUNCOMMAND_ACTION); diff --git a/wsmaster/che-core-api-factory/src/main/java/org/eclipse/che/api/factory/server/impl/FactoryCreateValidatorImpl.java b/wsmaster/che-core-api-factory/src/main/java/org/eclipse/che/api/factory/server/impl/FactoryCreateValidatorImpl.java index 168e768fb6..2c3b2273f1 100644 --- a/wsmaster/che-core-api-factory/src/main/java/org/eclipse/che/api/factory/server/impl/FactoryCreateValidatorImpl.java +++ b/wsmaster/che-core-api-factory/src/main/java/org/eclipse/che/api/factory/server/impl/FactoryCreateValidatorImpl.java @@ -14,7 +14,7 @@ import org.eclipse.che.api.core.BadRequestException; import org.eclipse.che.api.core.ForbiddenException; import org.eclipse.che.api.core.ServerException; import org.eclipse.che.api.factory.server.FactoryCreateValidator; -import org.eclipse.che.api.factory.shared.dto.Factory; +import org.eclipse.che.api.factory.shared.dto.FactoryDto; import org.eclipse.che.api.user.server.spi.PreferenceDao; import org.eclipse.che.api.workspace.server.WorkspaceValidator; @@ -29,16 +29,15 @@ public class FactoryCreateValidatorImpl extends FactoryBaseValidator implements private WorkspaceValidator workspaceConfigValidator; @Inject - public FactoryCreateValidatorImpl(PreferenceDao preferenceDao, - WorkspaceValidator workspaceConfigValidator) { - super(preferenceDao); + public FactoryCreateValidatorImpl(WorkspaceValidator workspaceConfigValidator) { this.workspaceConfigValidator = workspaceConfigValidator; } @Override - public void validateOnCreate(Factory factory) throws BadRequestException, ServerException, ForbiddenException { + public void validateOnCreate(FactoryDto factory) throws BadRequestException, + ServerException, + ForbiddenException { validateProjects(factory); - validateAccountId(factory); validateCurrentTimeAfterSinceUntil(factory); validateProjectActions(factory); workspaceConfigValidator.validateConfig(factory.getWorkspace()); diff --git a/wsmaster/che-core-api-factory/src/main/java/org/eclipse/che/api/factory/server/impl/FactoryEditValidatorImpl.java b/wsmaster/che-core-api-factory/src/main/java/org/eclipse/che/api/factory/server/impl/FactoryEditValidatorImpl.java index 68afee56f7..702630ec70 100644 --- a/wsmaster/che-core-api-factory/src/main/java/org/eclipse/che/api/factory/server/impl/FactoryEditValidatorImpl.java +++ b/wsmaster/che-core-api-factory/src/main/java/org/eclipse/che/api/factory/server/impl/FactoryEditValidatorImpl.java @@ -13,8 +13,8 @@ package org.eclipse.che.api.factory.server.impl; import org.eclipse.che.api.core.ForbiddenException; import org.eclipse.che.api.core.ServerException; import org.eclipse.che.api.factory.server.FactoryEditValidator; -import org.eclipse.che.api.factory.shared.dto.Author; -import org.eclipse.che.api.factory.shared.dto.Factory; +import org.eclipse.che.api.core.model.factory.Author; +import org.eclipse.che.api.core.model.factory.Factory; import org.eclipse.che.commons.env.EnvironmentContext; import javax.inject.Singleton; @@ -42,7 +42,7 @@ public class FactoryEditValidatorImpl implements FactoryEditValidator { @Override public void validate(Factory factory) throws ForbiddenException, ServerException { // Checks if there is an author from the factory (It may be missing for some old factories) - Author author = factory.getCreator(); + final Author author = factory.getCreator(); if (author == null || author.getUserId() == null) { throw new ServerException(format("Invalid factory without author stored. Please contact the support about the factory ID '%s'", factory.getId())); @@ -51,7 +51,7 @@ public class FactoryEditValidatorImpl implements FactoryEditValidator { final String userId = EnvironmentContext.getCurrent().getSubject().getUserId(); if (!author.getUserId().equals(userId)) { throw new ForbiddenException(format("You are not authorized for the factory '%s'", - factory.getId())); + factory.getId())); } } } diff --git a/wsmaster/che-core-api-factory/src/main/java/org/eclipse/che/api/factory/server/jpa/FactoryJpaModule.java b/wsmaster/che-core-api-factory/src/main/java/org/eclipse/che/api/factory/server/jpa/FactoryJpaModule.java new file mode 100644 index 0000000000..9b8d18889f --- /dev/null +++ b/wsmaster/che-core-api-factory/src/main/java/org/eclipse/che/api/factory/server/jpa/FactoryJpaModule.java @@ -0,0 +1,27 @@ +/******************************************************************************* + * Copyright (c) 2012-2016 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 + *******************************************************************************/ +package org.eclipse.che.api.factory.server.jpa; + +import com.google.inject.AbstractModule; + +import org.eclipse.che.api.factory.server.jpa.JpaFactoryDao.RemoveFactoriesBeforeUserRemovedEventSubscriber; +import org.eclipse.che.api.factory.server.spi.FactoryDao; + +/** + * @author Yevhenii Voevodin + */ +public class FactoryJpaModule extends AbstractModule { + @Override + protected void configure() { + bind(FactoryDao.class).to(JpaFactoryDao.class); + bind(RemoveFactoriesBeforeUserRemovedEventSubscriber.class).asEagerSingleton(); + } +} diff --git a/wsmaster/che-core-api-factory/src/main/java/org/eclipse/che/api/factory/server/jpa/JpaFactoryDao.java b/wsmaster/che-core-api-factory/src/main/java/org/eclipse/che/api/factory/server/jpa/JpaFactoryDao.java new file mode 100644 index 0000000000..d3629ecb11 --- /dev/null +++ b/wsmaster/che-core-api-factory/src/main/java/org/eclipse/che/api/factory/server/jpa/JpaFactoryDao.java @@ -0,0 +1,194 @@ +/******************************************************************************* + * Copyright (c) 2012-2016 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 + *******************************************************************************/ +package org.eclipse.che.api.factory.server.jpa; + +import com.google.inject.persist.Transactional; + +import org.eclipse.che.api.core.ConflictException; +import org.eclipse.che.api.core.NotFoundException; +import org.eclipse.che.api.core.ServerException; +import org.eclipse.che.api.core.jdbc.jpa.DuplicateKeyException; +import org.eclipse.che.api.core.jdbc.jpa.IntegrityConstraintViolationException; +import org.eclipse.che.api.core.notification.EventService; +import org.eclipse.che.api.core.notification.EventSubscriber; +import org.eclipse.che.api.factory.server.model.impl.FactoryImpl; +import org.eclipse.che.api.factory.server.spi.FactoryDao; +import org.eclipse.che.api.user.server.event.BeforeUserRemovedEvent; +import org.eclipse.che.commons.lang.NameGenerator; +import org.eclipse.che.commons.lang.Pair; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import javax.annotation.PostConstruct; +import javax.annotation.PreDestroy; +import javax.inject.Inject; +import javax.inject.Provider; +import javax.inject.Singleton; +import javax.persistence.EntityManager; +import java.util.Collections; +import javax.persistence.TypedQuery; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.StringJoiner; + +import static java.lang.String.format; +import static java.util.Collections.singletonList; +import static java.util.Objects.requireNonNull; + +/** + * @author Anton Korneta + */ +@Singleton +public class JpaFactoryDao implements FactoryDao { + private static final Logger LOG = LoggerFactory.getLogger(JpaFactoryDao.class); + + @Inject + private Provider managerProvider; + + @Override + public FactoryImpl create(FactoryImpl factory) throws ConflictException, ServerException { + requireNonNull(factory); + try { + doCreate(factory); + } catch (DuplicateKeyException ex) { + throw new ConflictException(ex.getLocalizedMessage()); + } catch (IntegrityConstraintViolationException ex) { + throw new ConflictException("Could not create factory with creator that refers on non-existent user"); + } catch (RuntimeException ex) { + throw new ServerException(ex.getLocalizedMessage(), ex); + } + return factory; + } + + @Override + public FactoryImpl update(FactoryImpl update) throws NotFoundException, ConflictException, ServerException { + requireNonNull(update); + try { + return doUpdate(update); + } catch (DuplicateKeyException ex) { + throw new ConflictException(ex.getLocalizedMessage()); + } catch (RuntimeException ex) { + throw new ServerException(ex.getLocalizedMessage(), ex); + } + } + + @Override + public void remove(String id) throws ServerException { + requireNonNull(id); + try { + doRemove(id); + } catch (RuntimeException ex) { + throw new ServerException(ex.getLocalizedMessage(), ex); + } + } + + @Override + @Transactional + public FactoryImpl getById(String id) throws NotFoundException, ServerException { + requireNonNull(id); + try { + final FactoryImpl factory = managerProvider.get().find(FactoryImpl.class, id); + if (factory == null) { + throw new NotFoundException(format("Factory with id '%s' doesn't exist", id)); + } + return factory; + } catch (RuntimeException ex) { + throw new ServerException(ex.getLocalizedMessage(), ex); + } + } + + @Override + @Transactional + public List getByAttribute(int maxItems, + int skipCount, + List> attributes) throws ServerException { + try { + LOG.info("FactoryDao#getByAttributes #maxItems: {} #skipCount: {}, #attributes: {}", maxItems, skipCount, attributes); + final Map params = new HashMap<>(); + String query = "SELECT factory FROM Factory factory"; + if (!attributes.isEmpty()) { + final StringJoiner matcher = new StringJoiner(" AND ", " WHERE ", " "); + int i = 0; + for (Pair attribute : attributes) { + final String parameterName = "parameterName" + i++; + params.put(parameterName, attribute.second); + matcher.add("factory." + attribute.first + " = :" + parameterName); + } + query = query + matcher; + } + final TypedQuery typedQuery = managerProvider.get() + .createQuery(query, FactoryImpl.class) + .setFirstResult(skipCount) + .setMaxResults(maxItems); + for (Map.Entry entry : params.entrySet()) { + typedQuery.setParameter(entry.getKey(), entry.getValue()); + } + return typedQuery.getResultList(); + } catch (RuntimeException ex) { + throw new ServerException(ex.getLocalizedMessage(), ex); + } + } + + @Transactional + protected void doCreate(FactoryImpl factory) { + final EntityManager manager = managerProvider.get(); + manager.persist(factory); + } + + @Transactional + protected FactoryImpl doUpdate(FactoryImpl update) throws NotFoundException { + final EntityManager manager = managerProvider.get(); + if (manager.find(FactoryImpl.class, update.getId()) == null) { + throw new NotFoundException(format("Could not update factory with id %s because it doesn't exist", update.getId())); + } + return manager.merge(update); + } + + @Transactional + protected void doRemove(String id) { + final EntityManager manager = managerProvider.get(); + final FactoryImpl factory = manager.find(FactoryImpl.class, id); + if (factory != null) { + manager.remove(factory); + } + } + + @Singleton + public static class RemoveFactoriesBeforeUserRemovedEventSubscriber implements EventSubscriber { + @Inject + private FactoryDao factoryDao; + @Inject + private EventService eventService; + + @PostConstruct + public void subscribe() { + eventService.subscribe(this); + } + + @PreDestroy + public void unsubscribe() { + eventService.unsubscribe(this); + } + + @Override + public void onEvent(BeforeUserRemovedEvent event) { + try { + final Pair factoryCreator = Pair.of("creator.userId", event.getUser().getId()); + for (FactoryImpl factory : factoryDao.getByAttribute(0, 0, singletonList(factoryCreator))) { + factoryDao.remove(factory.getId()); + } + } catch (Exception x) { + LOG.error(format("Couldn't remove factories before user '%s' removed", event.getUser().getId()), x); + } + } + } +} diff --git a/wsmaster/che-core-api-factory/src/main/java/org/eclipse/che/api/factory/server/model/impl/ActionImpl.java b/wsmaster/che-core-api-factory/src/main/java/org/eclipse/che/api/factory/server/model/impl/ActionImpl.java new file mode 100644 index 0000000000..7dbdf64ef5 --- /dev/null +++ b/wsmaster/che-core-api-factory/src/main/java/org/eclipse/che/api/factory/server/model/impl/ActionImpl.java @@ -0,0 +1,98 @@ +/******************************************************************************* + * Copyright (c) 2012-2016 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 + *******************************************************************************/ +package org.eclipse.che.api.factory.server.model.impl; + +import org.eclipse.che.api.core.model.factory.Action; + +import javax.persistence.Basic; +import javax.persistence.ElementCollection; +import javax.persistence.Entity; +import javax.persistence.GeneratedValue; +import javax.persistence.Id; +import java.util.HashMap; +import java.util.Map; +import java.util.Objects; + +/** + * Data object for {@link Action}. + * + * @author Anton Korneta + */ +@Entity(name = "Action") +public class ActionImpl implements Action { + + @Id + @GeneratedValue + private Long entityId; + + @Basic + private String id; + + @ElementCollection + private Map properties; + + public ActionImpl() {} + + public ActionImpl(String id, Map properties) { + this.id = id; + this.properties = properties; + } + + public ActionImpl(Action action) { + this(action.getId(), action.getProperties()); + } + + @Override + public String getId() { + return id; + } + + public void setId(String id) { + this.id = id; + } + + @Override + public Map getProperties() { + if (properties == null) { + return new HashMap<>(); + } + return properties; + } + + public void setProperties(Map properties) { + this.properties = properties; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) return true; + if (!(obj instanceof ActionImpl)) return false; + final ActionImpl other = (ActionImpl)obj; + return Objects.equals(id, other.getId()) + && getProperties().equals(other.getProperties()); + } + + @Override + public int hashCode() { + int result = 7; + result = 31 * result + Objects.hashCode(id); + result = 31 * result + getProperties().hashCode(); + return result; + } + + @Override + public String toString() { + return "ActionImpl{" + + "id='" + id + '\'' + + ", properties=" + properties + + '}'; + } +} diff --git a/wsmaster/che-core-api-factory/src/main/java/org/eclipse/che/api/factory/server/model/impl/AuthorImpl.java b/wsmaster/che-core-api-factory/src/main/java/org/eclipse/che/api/factory/server/model/impl/AuthorImpl.java new file mode 100644 index 0000000000..0ee622dbbd --- /dev/null +++ b/wsmaster/che-core-api-factory/src/main/java/org/eclipse/che/api/factory/server/model/impl/AuthorImpl.java @@ -0,0 +1,86 @@ +/******************************************************************************* + * Copyright (c) 2012-2016 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 + *******************************************************************************/ +package org.eclipse.che.api.factory.server.model.impl; + +import org.eclipse.che.api.core.model.factory.Author; + +import javax.persistence.Basic; +import javax.persistence.Embeddable; +import java.util.Objects; + +/** + * Data object for {@link Author}. + * + * @author Anton Korneta + */ +@Embeddable +public class AuthorImpl implements Author { + + @Basic + private Long created; + + @Basic + private String userId; + + public AuthorImpl() {} + + public AuthorImpl(String userId, Long created) { + this.created = created; + this.userId = userId; + } + + public AuthorImpl(Author creator) { + this(creator.getUserId(), creator.getCreated()); + } + + @Override + public Long getCreated() { + return created; + } + + public void setCreated(Long created) { + this.created = created; + } + + @Override + public String getUserId() { + return userId; + } + + public void setUserId(String userId) { + this.userId = userId; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) return true; + if (!(obj instanceof AuthorImpl)) return false; + final AuthorImpl other = (AuthorImpl)obj; + return Objects.equals(userId, other.userId) + && Objects.equals(created, other.created); + } + + @Override + public int hashCode() { + int result = 7; + result = 31 * result + Objects.hashCode(userId); + result = 31 * result + Long.hashCode(created); + return result; + } + + @Override + public String toString() { + return "AuthorImpl{" + + "created=" + created + + ", userId='" + userId + '\'' + + '}'; + } +} diff --git a/wsmaster/che-core-api-factory/src/main/java/org/eclipse/che/api/factory/server/model/impl/ButtonAttributesImpl.java b/wsmaster/che-core-api-factory/src/main/java/org/eclipse/che/api/factory/server/model/impl/ButtonAttributesImpl.java new file mode 100644 index 0000000000..5def3b61c4 --- /dev/null +++ b/wsmaster/che-core-api-factory/src/main/java/org/eclipse/che/api/factory/server/model/impl/ButtonAttributesImpl.java @@ -0,0 +1,124 @@ +/******************************************************************************* + * Copyright (c) 2012-2016 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 + *******************************************************************************/ +package org.eclipse.che.api.factory.server.model.impl; + +import org.eclipse.che.api.core.model.factory.ButtonAttributes; + +import javax.persistence.Basic; +import javax.persistence.Embeddable; +import java.util.Objects; + +/** + * Data object for {@link ButtonAttributes}. + * + * @author Anton Korneta + */ +@Embeddable +public class ButtonAttributesImpl implements ButtonAttributes { + + @Basic + private String color; + + @Basic + private String logo; + + @Basic + private String style; + + @Basic + private Boolean counter; + + public ButtonAttributesImpl() {} + + public ButtonAttributesImpl(String color, + String logo, + String style, + Boolean counter) { + this.color = color; + this.logo = logo; + this.style = style; + this.counter = counter; + } + + public ButtonAttributesImpl(ButtonAttributes attributes) { + this(attributes.getColor(), + attributes.getLogo(), + attributes.getStyle(), + attributes.getCounter()); + } + + @Override + public String getColor() { + return color; + } + + public void setColor(String color) { + this.color = color; + } + + @Override + public String getLogo() { + return logo; + } + + public void setLogo(String logo) { + this.logo = logo; + } + + @Override + public String getStyle() { + return style; + } + + public void setStyle(String style) { + this.style = style; + } + + @Override + public Boolean getCounter() { + return counter; + } + + public void setCounter(Boolean counter) { + this.counter = counter; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) return true; + if (!(obj instanceof ButtonAttributesImpl)) return false; + final ButtonAttributesImpl other = (ButtonAttributesImpl)obj; + return Objects.equals(color, other.color) + && Objects.equals(logo, other.logo) + && Objects.equals(style, other.style) + && counter == other.counter; + } + + @Override + public int hashCode() { + int result = 7; + result = 31 * result + Objects.hashCode(color); + result = 31 * result + Objects.hashCode(logo); + result = 31 * result + Objects.hashCode(style); + result = 31 * result + Boolean.hashCode(counter); + return result; + } + + @Override + public String toString() { + return "ButtonAttributesImpl{" + + "color='" + color + '\'' + + ", logo='" + logo + '\'' + + ", style='" + style + '\'' + + ", counter=" + counter + + '}'; + } +} diff --git a/wsmaster/che-core-api-factory/src/main/java/org/eclipse/che/api/factory/server/model/impl/ButtonImpl.java b/wsmaster/che-core-api-factory/src/main/java/org/eclipse/che/api/factory/server/model/impl/ButtonImpl.java new file mode 100644 index 0000000000..a84dd92419 --- /dev/null +++ b/wsmaster/che-core-api-factory/src/main/java/org/eclipse/che/api/factory/server/model/impl/ButtonImpl.java @@ -0,0 +1,96 @@ +/******************************************************************************* + * Copyright (c) 2012-2016 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 + *******************************************************************************/ +package org.eclipse.che.api.factory.server.model.impl; + +import org.eclipse.che.api.core.model.factory.Button; +import org.eclipse.che.api.core.model.factory.ButtonAttributes; + +import javax.persistence.Embedded; +import javax.persistence.Entity; +import javax.persistence.EnumType; +import javax.persistence.Enumerated; +import javax.persistence.GeneratedValue; +import javax.persistence.Id; +import java.util.Objects; + +/** + * Data object for {@link Button}. + * + * @author Anton Korneta + */ +@Entity(name = "Button") +public class ButtonImpl implements Button { + + @Id + @GeneratedValue + private Long id; + + @Embedded + private ButtonAttributesImpl attributes; + + @Enumerated(EnumType.STRING) + private Type type; + + public ButtonImpl(ButtonAttributes attributes, + Type type) { + this.attributes = new ButtonAttributesImpl(attributes); + this.type = type; + } + + public ButtonImpl() {} + + public ButtonImpl(Button button) { + this(button.getAttributes(), button.getType()); + } + + @Override + public ButtonAttributesImpl getAttributes() { + return attributes; + } + + public void setAttributes(ButtonAttributesImpl attributes) { + this.attributes = attributes; + } + + @Override + public Type getType() { + return type; + } + + public void setType(Type type) { + this.type = type; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) return true; + if (!(obj instanceof ButtonImpl)) return false; + final ButtonImpl other = (ButtonImpl)obj; + return Objects.equals(attributes, other.attributes) + && Objects.equals(type, other.type); + } + + @Override + public int hashCode() { + int result = 7; + result = 31 * result + Objects.hashCode(attributes); + result = 31 * result + Objects.hashCode(type); + return result; + } + + @Override + public String toString() { + return "ButtonImpl{" + + "attributes=" + attributes + + ", type=" + type + + '}'; + } +} diff --git a/wsmaster/che-core-api-factory/src/main/java/org/eclipse/che/api/factory/server/model/impl/FactoryImpl.java b/wsmaster/che-core-api-factory/src/main/java/org/eclipse/che/api/factory/server/model/impl/FactoryImpl.java new file mode 100644 index 0000000000..c760bfe00a --- /dev/null +++ b/wsmaster/che-core-api-factory/src/main/java/org/eclipse/che/api/factory/server/model/impl/FactoryImpl.java @@ -0,0 +1,342 @@ +/******************************************************************************* + * Copyright (c) 2012-2016 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 + *******************************************************************************/ +package org.eclipse.che.api.factory.server.model.impl; + +import org.eclipse.che.api.core.model.factory.Author; +import org.eclipse.che.api.core.model.factory.Button; +import org.eclipse.che.api.core.model.factory.Factory; +import org.eclipse.che.api.core.model.factory.Ide; +import org.eclipse.che.api.core.model.factory.Policies; +import org.eclipse.che.api.core.model.workspace.WorkspaceConfig; +import org.eclipse.che.api.factory.server.FactoryImage; +import org.eclipse.che.api.user.server.model.impl.UserImpl; +import org.eclipse.che.api.workspace.server.model.impl.WorkspaceConfigImpl; +import org.eclipse.che.commons.lang.NameGenerator; + +import javax.persistence.CascadeType; +import javax.persistence.Column; +import javax.persistence.ElementCollection; +import javax.persistence.Embedded; +import javax.persistence.Entity; +import javax.persistence.Id; +import javax.persistence.JoinColumn; +import javax.persistence.OneToOne; +import javax.persistence.Table; +import java.util.HashSet; +import java.util.Objects; +import java.util.Set; + +/** + * Data object for {@link Factory}. + * + * @author Anton Korneta + */ +@Entity(name = "Factory") +@Table +// TODO fix after issue: https://github.com/eclipse/che/issues/2110 +//(uniqueConstraints = {@UniqueConstraint(columnNames = {"name", "userId"})}) +public class FactoryImpl implements Factory { + + public static FactoryImplBuilder builder() { + return new FactoryImplBuilder(); + } + + @Id + private String id; + + @Column(nullable = true) + private String name; + + @Column(nullable = false) + private String version; + + @OneToOne(cascade = CascadeType.ALL, orphanRemoval = true, optional = false) + private WorkspaceConfigImpl workspace; + + @Embedded + private AuthorImpl creator; + + @OneToOne + @JoinColumn(insertable = false, updatable = false, name = "userId") + private UserImpl userEntity; + + @OneToOne(cascade = CascadeType.ALL, orphanRemoval = true) + private ButtonImpl button; + + @OneToOne(cascade = CascadeType.ALL, orphanRemoval = true) + private IdeImpl ide; + + @Embedded + private PoliciesImpl policies; + + @ElementCollection + private Set images; + + public FactoryImpl() {} + + public FactoryImpl(String id, + String name, + String version, + WorkspaceConfig workspace, + Author creator, + Policies policies, + Ide ide, + Button button, + Set images) { + this.id = id; + this.name = name; + this.version = version; + if (workspace != null) { + this.workspace = new WorkspaceConfigImpl(workspace); + } + if (creator != null) { + this.creator = new AuthorImpl(creator); + } + if (policies != null) { + this.policies = new PoliciesImpl(policies); + } + if (ide != null) { + this.ide = new IdeImpl(ide); + } + if (button != null) { + this.button = new ButtonImpl(button); + } + if (images != null) { + this.images = new HashSet<>(images); + } + } + + public FactoryImpl(Factory factory, Set images) { + this(factory.getId(), + factory.getName(), + factory.getV(), + factory.getWorkspace(), + factory.getCreator(), + factory.getPolicies(), + factory.getIde(), + factory.getButton(), + images); + } + + @Override + public String getId() { + return id; + } + + public void setId(String id) { + this.id = id; + } + + @Override + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + @Override + public String getV() { + return version; + } + + public void setV(String version) { + this.version = version; + } + + @Override + public WorkspaceConfigImpl getWorkspace() { + return workspace; + } + + public void setWorkspace(WorkspaceConfigImpl workspace) { + this.workspace = workspace; + } + + @Override + public AuthorImpl getCreator() { + return creator; + } + + public void setCreator(AuthorImpl creator) { + this.creator = creator; + } + + @Override + public PoliciesImpl getPolicies() { + return policies; + } + + public void setPolicies(PoliciesImpl policies) { + this.policies = policies; + } + + @Override + public ButtonImpl getButton() { + return button; + } + + public void setButton(ButtonImpl button) { + this.button = button; + } + + @Override + public IdeImpl getIde() { + return ide; + } + + public void setIde(IdeImpl ide) { + this.ide = ide; + } + + public Set getImages() { + if (images == null) { + images = new HashSet<>(); + } + return images; + } + + public void setImages(Set images) { + this.images = images; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) return true; + if (!(obj instanceof FactoryImpl)) return false; + final FactoryImpl other = (FactoryImpl)obj; + return Objects.equals(id, other.id) + && Objects.equals(name, other.name) + && Objects.equals(version, other.version) + && Objects.equals(workspace, other.workspace) + && Objects.equals(creator, other.creator) + && Objects.equals(policies, other.policies) + && Objects.equals(ide, other.ide) + && Objects.equals(button, other.button) + && getImages().equals(other.getImages()); + } + + @Override + public int hashCode() { + int hash = 7; + hash = 31 * hash + Objects.hashCode(id); + hash = 31 * hash + Objects.hashCode(name); + hash = 31 * hash + Objects.hashCode(version); + hash = 31 * hash + Objects.hashCode(workspace); + hash = 31 * hash + Objects.hashCode(creator); + hash = 31 * hash + Objects.hashCode(policies); + hash = 31 * hash + Objects.hashCode(ide); + hash = 31 * hash + Objects.hashCode(button); + hash = 31 * hash + getImages().hashCode(); + return hash; + } + + @Override + public String toString() { + return "FactoryImpl{" + + "id='" + id + '\'' + + ", name='" + name + '\'' + + ", version='" + version + '\'' + + ", workspace=" + workspace + + ", creator=" + creator + + ", policies=" + policies + + ", ide=" + ide + + ", button=" + button + + ", images=" + images + + '}'; + } + + /** + * Helps to create the instance of {@link FactoryImpl}. + */ + public static class FactoryImplBuilder { + + private String id; + private String name; + private String version; + private WorkspaceConfig workspace; + private Author creator; + private Policies policies; + private Ide ide; + private Button button; + private Set images; + + private FactoryImplBuilder() {} + + public FactoryImpl build() { + return new FactoryImpl(id, name, version, workspace, creator, policies, ide, button, images); + } + + public FactoryImplBuilder from(FactoryImpl factory) { + this.id = factory.getId(); + this.name = factory.getName(); + this.version = factory.getV(); + this.workspace = factory.getWorkspace(); + this.creator = factory.getCreator(); + this.policies = factory.getPolicies(); + this.ide = factory.getIde(); + this.button = factory.getButton(); + this.images = factory.getImages(); + return this; + } + + public FactoryImplBuilder generateId() { + id = NameGenerator.generate("", 16); + return this; + } + + public FactoryImplBuilder setId(String id) { + this.id = id; + return this; + } + + public FactoryImplBuilder setName(String name) { + this.name = name; + return this; + } + + public FactoryImplBuilder setVersion(String version) { + this.version = version; + return this; + } + + public FactoryImplBuilder setWorkspace(WorkspaceConfig workspace) { + this.workspace = workspace; + return this; + } + + public FactoryImplBuilder setCreator(Author creator) { + this.creator = creator; + return this; + } + + public FactoryImplBuilder setPolicies(Policies policies) { + this.policies = policies; + return this; + } + + public FactoryImplBuilder setIde(Ide ide) { + this.ide = ide; + return this; + } + + public FactoryImplBuilder setButton(Button button) { + this.button = button; + return this; + } + + public FactoryImplBuilder setImages(Set images) { + this.images = images; + return this; + } + } +} diff --git a/wsmaster/che-core-api-factory/src/main/java/org/eclipse/che/api/factory/server/model/impl/IdeImpl.java b/wsmaster/che-core-api-factory/src/main/java/org/eclipse/che/api/factory/server/model/impl/IdeImpl.java new file mode 100644 index 0000000000..e818b797a7 --- /dev/null +++ b/wsmaster/che-core-api-factory/src/main/java/org/eclipse/che/api/factory/server/model/impl/IdeImpl.java @@ -0,0 +1,122 @@ +/******************************************************************************* + * Copyright (c) 2012-2016 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 + *******************************************************************************/ +package org.eclipse.che.api.factory.server.model.impl; + +import org.eclipse.che.api.core.model.factory.Ide; +import org.eclipse.che.api.core.model.factory.OnAppClosed; +import org.eclipse.che.api.core.model.factory.OnAppLoaded; +import org.eclipse.che.api.core.model.factory.OnProjectsLoaded; + +import javax.persistence.CascadeType; +import javax.persistence.Entity; +import javax.persistence.GeneratedValue; +import javax.persistence.Id; +import javax.persistence.OneToOne; +import java.util.Objects; + +/** + * Data object for {@link Ide}. + * + * @author Anton Korneta + */ +@Entity(name = "Ide") +public class IdeImpl implements Ide { + + @Id + @GeneratedValue + private Long id; + + @OneToOne(cascade = CascadeType.ALL, orphanRemoval = true) + private OnAppLoadedImpl onAppLoaded; + + @OneToOne(cascade = CascadeType.ALL, orphanRemoval = true) + private OnProjectsLoadedImpl onProjectsLoaded; + + @OneToOne(cascade = CascadeType.ALL, orphanRemoval = true) + private OnAppClosedImpl onAppClosed; + + public IdeImpl() {} + + public IdeImpl(OnAppLoaded onAppLoaded, + OnProjectsLoaded onProjectsLoaded, + OnAppClosed onAppClosed) { + if (onAppLoaded != null) { + this.onAppLoaded = new OnAppLoadedImpl(onAppLoaded); + } + if (onProjectsLoaded != null) { + this.onProjectsLoaded = new OnProjectsLoadedImpl(onProjectsLoaded); + } + if (onAppClosed != null) { + this.onAppClosed = new OnAppClosedImpl(onAppClosed); + } + } + + public IdeImpl(Ide ide) { + this(ide.getOnAppLoaded(), + ide.getOnProjectsLoaded(), + ide.getOnAppClosed()); + } + + @Override + public OnAppLoadedImpl getOnAppLoaded() { + return onAppLoaded; + } + + public void setOnAppLoaded(OnAppLoadedImpl onAppLoaded) { + this.onAppLoaded = onAppLoaded; + } + + @Override + public OnProjectsLoadedImpl getOnProjectsLoaded() { + return onProjectsLoaded; + } + + public void setOnProjectsLoaded(OnProjectsLoadedImpl onProjectsLoaded) { + this.onProjectsLoaded = onProjectsLoaded; + } + + @Override + public OnAppClosedImpl getOnAppClosed() { + return onAppClosed; + } + + public void setOnAppClosed(OnAppClosedImpl onAppClosed) { + this.onAppClosed = onAppClosed; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) return true; + if (!(obj instanceof IdeImpl)) return false; + final IdeImpl other = (IdeImpl)obj; + return Objects.equals(onAppLoaded, other.onAppLoaded) + && Objects.equals(onProjectsLoaded, other.onProjectsLoaded) + && Objects.equals(onAppClosed, other.onAppClosed); + } + + @Override + public int hashCode() { + int result = 7; + result = 31 * result + Objects.hashCode(onAppLoaded); + result = 31 * result + Objects.hashCode(onProjectsLoaded); + result = 31 * result + Objects.hashCode(onAppClosed); + return result; + } + + @Override + public String toString() { + return "IdeImpl{" + + "onAppLoaded=" + onAppLoaded + + ", onProjectsLoaded=" + onProjectsLoaded + + ", onAppClosed=" + onAppClosed + + '}'; + } +} diff --git a/wsmaster/che-core-api-factory/src/main/java/org/eclipse/che/api/factory/server/model/impl/OnAppClosedImpl.java b/wsmaster/che-core-api-factory/src/main/java/org/eclipse/che/api/factory/server/model/impl/OnAppClosedImpl.java new file mode 100644 index 0000000000..aa2d7b66a7 --- /dev/null +++ b/wsmaster/che-core-api-factory/src/main/java/org/eclipse/che/api/factory/server/model/impl/OnAppClosedImpl.java @@ -0,0 +1,86 @@ +/******************************************************************************* + * Copyright (c) 2012-2016 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 + *******************************************************************************/ +package org.eclipse.che.api.factory.server.model.impl; + +import org.eclipse.che.api.core.model.factory.Action; +import org.eclipse.che.api.core.model.factory.OnAppClosed; + +import javax.persistence.Entity; +import javax.persistence.GeneratedValue; +import javax.persistence.Id; +import javax.persistence.OneToMany; +import java.util.ArrayList; +import java.util.List; + +import static java.util.stream.Collectors.toList; +import static javax.persistence.CascadeType.ALL; + +/** + * Data object for {@link OnAppClosed}. + * + * @author Anton Korneta + */ +@Entity(name = "OnAppClosed") +public class OnAppClosedImpl implements OnAppClosed { + + @Id + @GeneratedValue + private Long id; + + @OneToMany(cascade = ALL, orphanRemoval = true) + private List actions; + + public OnAppClosedImpl() {} + + public OnAppClosedImpl(List actions) { + if (actions != null) { + this.actions = actions.stream() + .map(ActionImpl::new) + .collect(toList()); + } + } + + public OnAppClosedImpl(OnAppClosed onAppClosed) { + this(onAppClosed.getActions()); + } + + @Override + public List getActions() { + if (actions == null) { + return new ArrayList<>(); + } + return actions; + } + + public void setActions(List actions) { + this.actions = actions; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) return true; + if (!(obj instanceof OnAppClosedImpl)) return false; + final OnAppClosedImpl other = (OnAppClosedImpl)obj; + return getActions().equals(other.getActions()); + } + + @Override + public int hashCode() { + return getActions().hashCode(); + } + + @Override + public String toString() { + return "OnAppClosedImpl{" + + "actions=" + actions + + '}'; + } +} diff --git a/wsmaster/che-core-api-factory/src/main/java/org/eclipse/che/api/factory/server/model/impl/OnAppLoadedImpl.java b/wsmaster/che-core-api-factory/src/main/java/org/eclipse/che/api/factory/server/model/impl/OnAppLoadedImpl.java new file mode 100644 index 0000000000..169d0e7db6 --- /dev/null +++ b/wsmaster/che-core-api-factory/src/main/java/org/eclipse/che/api/factory/server/model/impl/OnAppLoadedImpl.java @@ -0,0 +1,86 @@ +/******************************************************************************* + * Copyright (c) 2012-2016 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 + *******************************************************************************/ +package org.eclipse.che.api.factory.server.model.impl; + +import org.eclipse.che.api.core.model.factory.Action; +import org.eclipse.che.api.core.model.factory.OnAppLoaded; + +import javax.persistence.Entity; +import javax.persistence.GeneratedValue; +import javax.persistence.Id; +import javax.persistence.OneToMany; +import java.util.ArrayList; +import java.util.List; + +/** + * Data object for {@link OnAppLoaded}. + * + * @author Anton Korneta + */ +import static java.util.stream.Collectors.toList; +import static javax.persistence.CascadeType.ALL; + +@Entity(name = "OnAppLoaded") +public class OnAppLoadedImpl implements OnAppLoaded { + + @Id + @GeneratedValue + private Long id; + + @OneToMany(cascade = ALL, orphanRemoval = true) + private List actions; + + public OnAppLoadedImpl() {} + + public OnAppLoadedImpl(List actions) { + if (actions != null) { + this.actions = actions.stream() + .map(ActionImpl::new) + .collect(toList()); + } + } + + public OnAppLoadedImpl(OnAppLoaded onAppLoaded) { + this(onAppLoaded.getActions()); + } + + @Override + public List getActions() { + if (actions == null) { + return new ArrayList<>(); + } + return actions; + } + + public void setActions(List actions) { + this.actions = actions; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) return true; + if (!(obj instanceof OnAppLoadedImpl)) return false; + final OnAppLoadedImpl other = (OnAppLoadedImpl)obj; + return getActions().equals(other.getActions()); + } + + @Override + public int hashCode() { + return getActions().hashCode(); + } + + @Override + public String toString() { + return "OnAppLoadedImpl{" + + "actions=" + actions + + '}'; + } +} diff --git a/wsmaster/che-core-api-factory/src/main/java/org/eclipse/che/api/factory/server/model/impl/OnProjectsLoadedImpl.java b/wsmaster/che-core-api-factory/src/main/java/org/eclipse/che/api/factory/server/model/impl/OnProjectsLoadedImpl.java new file mode 100644 index 0000000000..fcf9862f62 --- /dev/null +++ b/wsmaster/che-core-api-factory/src/main/java/org/eclipse/che/api/factory/server/model/impl/OnProjectsLoadedImpl.java @@ -0,0 +1,86 @@ +/******************************************************************************* + * Copyright (c) 2012-2016 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 + *******************************************************************************/ +package org.eclipse.che.api.factory.server.model.impl; + +import org.eclipse.che.api.core.model.factory.Action; +import org.eclipse.che.api.core.model.factory.OnProjectsLoaded; + +import javax.persistence.CascadeType; +import javax.persistence.Entity; +import javax.persistence.GeneratedValue; +import javax.persistence.Id; +import javax.persistence.OneToMany; +import java.util.ArrayList; +import java.util.List; + +import static java.util.stream.Collectors.toList; + +/** + * Data object for {@link OnProjectsLoaded}. + * + * @author Anton Korneta + */ +@Entity(name = "OnProjectsLoaded") +public class OnProjectsLoadedImpl implements OnProjectsLoaded { + + @Id + @GeneratedValue + private Long id; + + @OneToMany(cascade = CascadeType.ALL, orphanRemoval = true) + private List actions; + + public OnProjectsLoadedImpl() {} + + public OnProjectsLoadedImpl(List actions) { + if (actions != null) { + this.actions = actions.stream() + .map(ActionImpl::new) + .collect(toList()); + } + } + + public OnProjectsLoadedImpl(OnProjectsLoaded onProjectsLoaded) { + this(onProjectsLoaded.getActions()); + } + + @Override + public List getActions() { + if (actions == null) { + return new ArrayList<>(); + } + return actions; + } + + public void setActions(List actions) { + this.actions = actions; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) return true; + if (!(obj instanceof OnProjectsLoadedImpl)) return false; + final OnProjectsLoadedImpl other = (OnProjectsLoadedImpl)obj; + return getActions().equals(other.getActions()); + } + + @Override + public int hashCode() { + return getActions().hashCode(); + } + + @Override + public String toString() { + return "OnProjectsLoadedImpl{" + + "actions=" + actions + + '}'; + } +} diff --git a/wsmaster/che-core-api-factory/src/main/java/org/eclipse/che/api/factory/server/model/impl/PoliciesImpl.java b/wsmaster/che-core-api-factory/src/main/java/org/eclipse/che/api/factory/server/model/impl/PoliciesImpl.java new file mode 100644 index 0000000000..d0d39cc6cf --- /dev/null +++ b/wsmaster/che-core-api-factory/src/main/java/org/eclipse/che/api/factory/server/model/impl/PoliciesImpl.java @@ -0,0 +1,143 @@ +/******************************************************************************* + * Copyright (c) 2012-2016 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 + *******************************************************************************/ +package org.eclipse.che.api.factory.server.model.impl; + +import org.eclipse.che.api.core.model.factory.Policies; + +import javax.persistence.Basic; +import javax.persistence.Column; +import javax.persistence.Embeddable; +import java.util.Objects; + +/** + * Data object for {@link Policies}. + * + * @author Anton Korneta + */ +@Embeddable +public class PoliciesImpl implements Policies { + + @Basic + private String referer; + + @Column(name = "match_reopen") + private String match; + + @Column(name = "creation_strategy") + private String create; + + @Basic + private Long until; + + @Basic + private Long since; + + public PoliciesImpl() {} + + public PoliciesImpl(String referer, + String match, + String create, + Long until, + Long since) { + this.referer = referer; + this.match = match; + this.create = create; + this.until = until; + this.since = since; + } + + public PoliciesImpl(Policies policies) { + this(policies.getReferer(), + policies.getMatch(), + policies.getCreate(), + policies.getUntil(), + policies.getSince()); + } + + @Override + public String getReferer() { + return referer; + } + + public void setReferer(String referer) { + this.referer = referer; + } + + @Override + public String getMatch() { + return match; + } + + public void setMatch(String match) { + this.match = match; + } + + @Override + public String getCreate() { + return create; + } + + public void setCreate(String create) { + this.create = create; + } + + @Override + public Long getUntil() { + return until; + } + + public void setUntil(Long until) { + this.until = until; + } + + @Override + public Long getSince() { + return since; + } + + public void setSince(Long since) { + this.since = since; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) return true; + if (!(obj instanceof PoliciesImpl)) return false; + final PoliciesImpl other = (PoliciesImpl)obj; + return Objects.equals(referer, other.referer) + && Objects.equals(match, other.match) + && Objects.equals(create, other.create) + && Objects.equals(until, other.until) + && Objects.equals(since, other.since); + } + + @Override + public int hashCode() { + int result = 7; + result = 31 * result + Objects.hashCode(referer); + result = 31 * result + Objects.hashCode(match); + result = 31 * result + Objects.hashCode(create); + result = 31 * result + Long.hashCode(until); + result = 31 * result + Long.hashCode(since); + return result; + } + + @Override + public String toString() { + return "PoliciesImpl{" + + "referer='" + referer + '\'' + + ", match='" + match + '\'' + + ", create='" + create + '\'' + + ", until=" + until + + ", since=" + since + + '}'; + } +} diff --git a/wsmaster/che-core-api-factory/src/main/java/org/eclipse/che/api/factory/server/snippet/SnippetGenerator.java b/wsmaster/che-core-api-factory/src/main/java/org/eclipse/che/api/factory/server/snippet/SnippetGenerator.java index 336988400e..299eba7e1e 100644 --- a/wsmaster/che-core-api-factory/src/main/java/org/eclipse/che/api/factory/server/snippet/SnippetGenerator.java +++ b/wsmaster/che-core-api-factory/src/main/java/org/eclipse/che/api/factory/server/snippet/SnippetGenerator.java @@ -10,8 +10,8 @@ *******************************************************************************/ package org.eclipse.che.api.factory.server.snippet; -import org.eclipse.che.api.factory.shared.dto.Button; -import org.eclipse.che.api.factory.shared.dto.Factory; +import org.eclipse.che.api.core.model.factory.Button; +import org.eclipse.che.api.core.model.factory.Factory; import javax.ws.rs.core.UriBuilder; import java.util.Formatter; @@ -37,7 +37,7 @@ public class SnippetGenerator { throw new IllegalArgumentException("Unable to generate markdown snippet for factory without button"); } - if (Button.ButtonType.logo.equals(factory.getButton().getType())) { + if (Button.Type.LOGO.equals(factory.getButton().getType())) { if (imageId != null && factory.getId() != null) { imgUrl = format("%s/api/factory/%s/image?imgId=%s", baseUrl, factory.getId(), imageId); } else { diff --git a/wsmaster/che-core-api-factory/src/main/java/org/eclipse/che/api/factory/server/spi/FactoryDao.java b/wsmaster/che-core-api-factory/src/main/java/org/eclipse/che/api/factory/server/spi/FactoryDao.java new file mode 100644 index 0000000000..f88b59fce2 --- /dev/null +++ b/wsmaster/che-core-api-factory/src/main/java/org/eclipse/che/api/factory/server/spi/FactoryDao.java @@ -0,0 +1,106 @@ +/******************************************************************************* + * Copyright (c) 2012-2016 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 + *******************************************************************************/ +package org.eclipse.che.api.factory.server.spi; + +import org.eclipse.che.api.core.ConflictException; +import org.eclipse.che.api.core.NotFoundException; +import org.eclipse.che.api.core.ServerException; +import org.eclipse.che.api.factory.server.model.impl.FactoryImpl; +import org.eclipse.che.commons.lang.Pair; + +import java.util.List; + +/** + * Defines data access object contract for {@code FactoryImpl}. + * + * @author Max Shaposhnik + * @author Anton Korneta + */ +public interface FactoryDao { + + /** + * Creates factory. + * + * @param factory + * factory to create + * @return created factory + * @throws NullPointerException + * when {@code factory} is null + * @throws ConflictException + * when {@code factory} with given name and creator already exists + * @throws ServerException + * when any other error occurs + */ + FactoryImpl create(FactoryImpl factory) throws ConflictException, ServerException; + + /** + * Updates factory to the new entity, using replacement strategy. + * + * @param factory + * factory to update + * @return updated factory + * @throws NullPointerException + * when {@code factory} is null + * @throws NotFoundException + * when given factory is not found + * @throws ConflictException + * when {@code factory} with given name is already exist for creator + * @throws ServerException + * when any other error occurs + */ + FactoryImpl update(FactoryImpl factory) throws NotFoundException, ConflictException, ServerException; + + /** + * Removes factory. + * + * @param id + * factory identifier + * @throws NullPointerException + * when {@code id} is null + * @throws ServerException + * when any other error occurs + */ + void remove(String id) throws ServerException; + + /** + * Gets factory by identifier. + * + * @param id + * factory identifier + * @return factory instance, never null + * @throws NullPointerException + * when {@code id} is null + * @throws NotFoundException + * when factory with given {@code id} is not found + * @throws ServerException + * when any other error occurs + */ + FactoryImpl getById(String id) throws NotFoundException, ServerException; + + /** + * Gets the factories for the list of attributes. + * + * @param maxItems + * the maximum count of items to fetch + * @param skipCount + * count of items which should be skipped + * @param attributes + * list of pairs of attributes to search for + * @return list of the factories which contain the specified attributes + * @throws IllegalArgumentException + * when {@code skipCount} or {@code maxItems} is negative + * @throws ServerException + * when any other error occurs + */ + List getByAttribute(int maxItems, + int skipCount, + List> attributes) throws ServerException; +} diff --git a/wsmaster/che-core-api-factory/src/test/java/org/eclipse/che/api/factory/server/FactoryServiceTest.java b/wsmaster/che-core-api-factory/src/test/java/org/eclipse/che/api/factory/server/FactoryServiceTest.java index ad9dff6d8d..cc83e006b6 100644 --- a/wsmaster/che-core-api-factory/src/test/java/org/eclipse/che/api/factory/server/FactoryServiceTest.java +++ b/wsmaster/che-core-api-factory/src/test/java/org/eclipse/che/api/factory/server/FactoryServiceTest.java @@ -10,1043 +10,523 @@ *******************************************************************************/ package org.eclipse.che.api.factory.server; +import com.google.common.collect.FluentIterable; +import com.google.common.collect.ImmutableList; +import com.google.common.collect.ImmutableMap; +import com.google.common.collect.ImmutableSet; +import com.google.gson.JsonSyntaxException; import com.jayway.restassured.http.ContentType; import com.jayway.restassured.response.Response; import org.eclipse.che.api.core.BadRequestException; import org.eclipse.che.api.core.NotFoundException; +import org.eclipse.che.api.core.model.factory.Factory; +import org.eclipse.che.api.core.model.project.ProjectConfig; +import org.eclipse.che.api.core.model.user.User; +import org.eclipse.che.api.core.model.workspace.WorkspaceConfig; import org.eclipse.che.api.core.model.workspace.WorkspaceStatus; import org.eclipse.che.api.core.rest.ApiExceptionMapper; -import org.eclipse.che.api.core.rest.shared.dto.Link; import org.eclipse.che.api.core.rest.shared.dto.ServiceError; +import org.eclipse.che.api.factory.server.FactoryService.FactoryParametersResolverHolder; import org.eclipse.che.api.factory.server.builder.FactoryBuilder; import org.eclipse.che.api.factory.server.impl.SourceStorageParametersValidator; -import org.eclipse.che.api.factory.shared.dto.Author; -import org.eclipse.che.api.factory.shared.dto.Button; -import org.eclipse.che.api.factory.shared.dto.ButtonAttributes; -import org.eclipse.che.api.factory.shared.dto.Factory; +import org.eclipse.che.api.factory.server.model.impl.AuthorImpl; +import org.eclipse.che.api.factory.server.model.impl.FactoryImpl; +import org.eclipse.che.api.factory.shared.dto.FactoryDto; import org.eclipse.che.api.machine.shared.dto.CommandDto; +import org.eclipse.che.api.user.server.PreferenceManager; +import org.eclipse.che.api.user.server.UserManager; import org.eclipse.che.api.user.server.model.impl.UserImpl; -import org.eclipse.che.api.user.server.spi.UserDao; import org.eclipse.che.api.workspace.server.WorkspaceManager; +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.shared.dto.EnvironmentDto; import org.eclipse.che.api.workspace.shared.dto.ProjectConfigDto; import org.eclipse.che.api.workspace.shared.dto.SourceStorageDto; -import org.eclipse.che.api.workspace.shared.dto.WorkspaceConfigDto; import org.eclipse.che.commons.env.EnvironmentContext; import org.eclipse.che.commons.json.JsonHelper; import org.eclipse.che.commons.lang.Pair; import org.eclipse.che.commons.subject.SubjectImpl; import org.eclipse.che.dto.server.DtoFactory; import org.everrest.assured.EverrestJetty; -import org.everrest.assured.JettyHttpServer; import org.everrest.core.Filter; import org.everrest.core.GenericContainerRequest; import org.everrest.core.RequestFilter; -import org.mockito.Matchers; import org.mockito.Mock; -import org.mockito.invocation.InvocationOnMock; -import org.mockito.stubbing.Answer; +import org.mockito.Mockito; import org.mockito.testng.MockitoTestNGListener; -import org.testng.ITestContext; import org.testng.annotations.BeforeMethod; -import org.testng.annotations.DataProvider; import org.testng.annotations.Listeners; import org.testng.annotations.Test; -import javax.ws.rs.HttpMethod; -import javax.ws.rs.core.MediaType; +import javax.ws.rs.core.UriInfo; import java.io.File; import java.io.IOException; -import java.io.UnsupportedEncodingException; +import java.io.InputStream; +import java.net.URI; import java.net.URL; import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; -import java.util.ArrayList; import java.util.Arrays; -import java.util.Collections; import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; -import java.util.stream.Collectors; import static com.jayway.restassured.RestAssured.given; -import static java.lang.Boolean.FALSE; -import static java.lang.Boolean.TRUE; import static java.lang.String.format; import static java.lang.String.valueOf; -import static java.net.URLEncoder.encode; +import static java.lang.Thread.currentThread; +import static java.util.Collections.emptyList; +import static java.util.Collections.emptySet; import static java.util.Collections.singletonList; import static java.util.Collections.singletonMap; -import static javax.ws.rs.core.Response.Status; +import static java.util.stream.Collectors.toSet; +import static javax.ws.rs.core.MediaType.APPLICATION_JSON; +import static javax.ws.rs.core.MediaType.TEXT_PLAIN; import static javax.ws.rs.core.Response.Status.BAD_REQUEST; -import static javax.ws.rs.core.Response.Status.NOT_FOUND; -import static javax.ws.rs.core.Response.Status.OK; -import static org.eclipse.che.api.factory.server.FactoryService.ERROR_NO_RESOLVER_AVAILABLE; +import static org.eclipse.che.api.factory.server.DtoConverter.asDto; import static org.eclipse.che.api.factory.server.FactoryService.VALIDATE_QUERY_PARAMETER; -import static org.eclipse.che.api.workspace.server.DtoConverter.asDto; +import static org.everrest.assured.JettyHttpServer.ADMIN_USER_NAME; +import static org.everrest.assured.JettyHttpServer.ADMIN_USER_PASSWORD; import static org.hamcrest.Matchers.equalTo; import static org.mockito.Matchers.any; import static org.mockito.Matchers.anyBoolean; -import static org.mockito.Matchers.anyInt; -import static org.mockito.Matchers.anyMap; +import static org.mockito.Matchers.anyMapOf; import static org.mockito.Matchers.anySetOf; import static org.mockito.Matchers.anyString; import static org.mockito.Matchers.eq; import static org.mockito.Mockito.doNothing; +import static org.mockito.Mockito.doReturn; import static org.mockito.Mockito.doThrow; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.never; import static org.mockito.Mockito.spy; +import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; import static org.testng.Assert.assertEquals; import static org.testng.Assert.assertNotNull; import static org.testng.Assert.assertTrue; +/** + * Tests for {@link FactoryService}. + * + * @author Anton Korneta + */ @Listeners(value = {EverrestJetty.class, MockitoTestNGListener.class}) public class FactoryServiceTest { - private final String CORRECT_FACTORY_ID = "correctFactoryId"; - private final String ILLEGAL_FACTORY_ID = "illegalFactoryId"; - private final String SERVICE_PATH = "/factory"; - private static final String userId = "id-2314"; - private final ApiExceptionMapper exceptionMapper = new ApiExceptionMapper(); + private static final String SERVICE_PATH = "/factory"; + private static final String FACTORY_ID = "correctFactoryId"; + private static final String FACTORY_NAME = "factory"; + private static final String USER_ID = "userId"; + private static final String USER_EMAIL = "email"; + private static final String WORKSPACE_NAME = "workspace"; + private static final String PROJECT_SOURCE_TYPE = "git"; + private static final String PROJECT_SOURCE_LOCATION = "http://github.com/codenvy/platform-api.git"; + private static final String FACTORY_IMAGE_MIME_TYPE = "image/jpeg"; + private static final String IMAGE_NAME = "image12"; - /** - * Path of the resolver REST service. - */ - private final String SERVICE_PATH_RESOLVER = SERVICE_PATH + "/resolver"; - private EnvironmentFilter filter = new EnvironmentFilter(); + private static final DtoFactory DTO = DtoFactory.getInstance(); @Mock - private FactoryStore factoryStore; - + private FactoryManager factoryManager; @Mock - private FactoryCreateValidator createValidator; - + private FactoryCreateValidator createValidator; @Mock - private FactoryAcceptValidator acceptValidator; - + private FactoryAcceptValidator acceptValidator; @Mock - private FactoryEditValidator editValidator; - + private PreferenceManager preferenceManager; @Mock - private WorkspaceManager workspaceManager; - + private UserManager userManager; @Mock - private UserDao userDao; - + private FactoryEditValidator editValidator; @Mock - private FactoryService.FactoryParametersResolverHolder factoryParametersResolverHolder; - - private FactoryBuilder factoryBuilder; - - private FactoryService factoryService; - private DtoFactory dto; - - /** - * Set of all resolvers available for the factory service. - */ + private WorkspaceManager workspaceManager; @Mock + private FactoryParametersResolverHolder factoryParametersResolverHolder; + @Mock + private UriInfo uriInfo; + + private FactoryBuilder factoryBuilderSpy; + + private User user; private Set factoryParametersResolvers; + private FactoryService service; + + @SuppressWarnings("unused") + private ApiExceptionMapper apiExceptionMapper; + @SuppressWarnings("unused") + private EnvironmentFilter environmentFilter; @BeforeMethod public void setUp() throws Exception { - //doNothing().when(acceptValidator).validateOnAccept(any(Factory.class)); - dto = DtoFactory.getInstance(); - factoryBuilder = spy(new FactoryBuilder(new SourceStorageParametersValidator())); - doNothing().when(factoryBuilder).checkValid(any(Factory.class)); - doNothing().when(factoryBuilder).checkValid(any(Factory.class), anyBoolean()); + factoryBuilderSpy = spy(new FactoryBuilder(new SourceStorageParametersValidator())); + factoryParametersResolvers = new HashSet<>(); + doNothing().when(factoryBuilderSpy).checkValid(any(FactoryDto.class)); + doNothing().when(factoryBuilderSpy).checkValid(any(FactoryDto.class), anyBoolean()); when(factoryParametersResolverHolder.getFactoryParametersResolvers()).thenReturn(factoryParametersResolvers); - when(userDao.getById(anyString())).thenReturn(new UserImpl(null, - null, - JettyHttpServer.ADMIN_USER_NAME, - null, - null)); - factoryService = new FactoryService(factoryStore, - createValidator, - acceptValidator, - editValidator, - new LinksHelper(), - factoryBuilder, - workspaceManager, - factoryParametersResolverHolder, - userDao); + user = new UserImpl(USER_ID, USER_EMAIL, ADMIN_USER_NAME); + when(userManager.getById(anyString())).thenReturn(user); + when(preferenceManager.find(USER_ID)).thenReturn(ImmutableMap.of("preference", "value")); + service = new FactoryService(factoryManager, + userManager, + preferenceManager, + createValidator, + acceptValidator, + editValidator, + factoryBuilderSpy, + workspaceManager, + factoryParametersResolverHolder); } @Filter public static class EnvironmentFilter implements RequestFilter { - public void doFilter(GenericContainerRequest request) { EnvironmentContext context = EnvironmentContext.getCurrent(); - context.setSubject(new SubjectImpl(JettyHttpServer.ADMIN_USER_NAME, userId, "token-2323", false)); + context.setSubject(new SubjectImpl(ADMIN_USER_NAME, USER_ID, ADMIN_USER_PASSWORD, false)); } - } - @Test - public void shouldReturnSavedFactoryIfUserDidNotUseSpecialMethod() throws Exception { - // given - Factory factory = prepareFactoryWithGivenStorage("git", "http://github.com/codenvy/platform-api.git").withId(CORRECT_FACTORY_ID); - factory.setCreator(dto.createDto(Author.class).withUserId(userId).withName(JettyHttpServer.ADMIN_USER_NAME)); - Factory expected = dto.clone(factory); + public void shouldSaveFactoryWithImagesFromFormData() throws Exception { + final Factory factory = createFactory(); + final FactoryDto factoryDto = asDto(factory, user); + when(factoryManager.saveFactory(any(FactoryDto.class), anySetOf(FactoryImage.class))).thenReturn(factory); + when(factoryManager.getById(FACTORY_ID)).thenReturn(factory); + doReturn(factoryDto).when(factoryBuilderSpy).build(any(InputStream.class)); - when(factoryStore.getFactory(CORRECT_FACTORY_ID)).thenReturn(factory); - when(factoryStore.getFactoryImages(CORRECT_FACTORY_ID, null)).thenReturn(Collections.emptySet()); - - // when - Response response = given().when() - .get(SERVICE_PATH + "/" + CORRECT_FACTORY_ID); - - // then - assertEquals(response.getStatusCode(), 200); - Factory responseFactory = dto.createDtoFromJson(response.getBody() - .asInputStream(), Factory.class); - responseFactory.setLinks(Collections.emptyList()); - assertEquals(responseFactory, expected); - } + final Response response = given().auth() + .basic(ADMIN_USER_NAME, ADMIN_USER_PASSWORD) + .multiPart("factory", JsonHelper.toJson(factoryDto), APPLICATION_JSON) + .multiPart("image", getImagePath().toFile(), FACTORY_IMAGE_MIME_TYPE) + .expect() + .statusCode(200) + .when() + .post(SERVICE_PATH); - @Test - public void shouldBeAbleToSaveFactory() throws Exception { - // given - Factory factory = prepareFactoryWithGivenStorage("git", "http://github.com/codenvy/platform-api.git"); - URL resource = Thread.currentThread().getContextClassLoader() - .getResource("100x100_image.jpeg"); - assertNotNull(resource); - Path path = Paths.get(resource.toURI()); - - FactorySaveAnswer factorySaveAnswer = new FactorySaveAnswer(); - when(factoryStore.saveFactory(any(Factory.class), anySetOf(FactoryImage.class))).then(factorySaveAnswer); - when(factoryStore.getFactory(CORRECT_FACTORY_ID)).then(factorySaveAnswer); - - // when, then - Response response = given().auth() - .basic(JettyHttpServer.ADMIN_USER_NAME, JettyHttpServer.ADMIN_USER_PASSWORD) - .multiPart("factory", JsonHelper.toJson(factory), MediaType.APPLICATION_JSON) - .multiPart("image", path.toFile(), "image/jpeg") - .when() - .post("/private" + SERVICE_PATH); - - assertEquals(response.getStatusCode(), 200); - Factory responseFactory = dto.createDtoFromJson(response.getBody().asInputStream(), Factory.class); - boolean found = false; - for (Link link : responseFactory.getLinks()) { - if (link.getRel().equals("image") && link.getProduces().equals("image/jpeg") && !link.getHref().isEmpty()) - found = true; - } + final FactoryDto result = getFromResponse(response, FactoryDto.class); + final boolean found = result.getLinks() + .stream() + .anyMatch(link -> link.getRel().equals("image") + && link.getProduces().equals(FACTORY_IMAGE_MIME_TYPE) + && !link.getHref().isEmpty()); + factoryDto.withLinks(result.getLinks()) + .getCreator() + .withCreated(result.getCreator().getCreated()); + assertEquals(result, factoryDto); assertTrue(found); } @Test - public void shouldReturnStatus400IfSaveRequestHaveNotFactoryInfo() throws Exception { + public void shouldSaveFactoryFromFormDataWithoutImages() throws Exception { + final Factory factory = createFactory(); + final FactoryDto factoryDto = asDto(factory, user); + when(factoryManager.saveFactory(any(FactoryDto.class), anySetOf(FactoryImage.class))).thenReturn(factory); + doReturn(factoryDto).when(factoryBuilderSpy).build(any(InputStream.class)); - // when, then - Response response = given().auth() - .basic(JettyHttpServer.ADMIN_USER_NAME, JettyHttpServer.ADMIN_USER_PASSWORD) - .multiPart("someOtherData", "Some content", MediaType.TEXT_PLAIN) - .expect() - .statusCode(BAD_REQUEST.getStatusCode()) - .when() - .post("/private" + SERVICE_PATH); - - assertEquals(dto.createDtoFromJson(response.getBody().asInputStream(), ServiceError.class).getMessage(), - "No factory information found in 'factory' section of multipart/form-data."); + final Response response = given().auth() + .basic(ADMIN_USER_NAME, ADMIN_USER_PASSWORD) + .multiPart("factory", JsonHelper.toJson(factoryDto), APPLICATION_JSON) + .expect() + .statusCode(200) + .when() + .post(SERVICE_PATH); + final FactoryDto result = getFromResponse(response, FactoryDto.class); + factoryDto.withLinks(result.getLinks()) + .getCreator() + .withCreated(result.getCreator().getCreated()); + assertEquals(result, factoryDto); } @Test - public void shouldBeAbleToSaveFactoryWithOutImage(ITestContext context) throws Exception { - // given - Factory factory = prepareFactoryWithGivenStorage("git", "http://github.com/codenvy/platform-api.git"); + public void shouldSaveFactoryWithImagesWhenImagesWithoutContent() throws Exception { + final Factory factory = createFactory(); + when(factoryManager.saveFactory(any(FactoryDto.class), anySetOf(FactoryImage.class))).thenReturn(factory); + when(factoryManager.getById(FACTORY_ID)).thenReturn(factory); + final FactoryDto factoryDto = asDto(factory, user); + doReturn(factoryDto).when(factoryBuilderSpy).build(any(InputStream.class)); - Link expectedCreateProject = - dto.createDto(Link.class).withMethod(HttpMethod.GET).withProduces("text/html").withRel("accept") - .withHref(getServerUrl(context) + "/f?id=" + CORRECT_FACTORY_ID); - - FactorySaveAnswer factorySaveAnswer = new FactorySaveAnswer(); - when(factoryStore.saveFactory(any(Factory.class), anySetOf(FactoryImage.class))).then(factorySaveAnswer); - when(factoryStore.getFactory(CORRECT_FACTORY_ID)).then(factorySaveAnswer); - - // when, then - Response response = - given().auth().basic(JettyHttpServer.ADMIN_USER_NAME, JettyHttpServer.ADMIN_USER_PASSWORD)// - .multiPart("factory", JsonHelper.toJson(factory), MediaType.APPLICATION_JSON).when() - .post("/private" + SERVICE_PATH); - - // then - assertEquals(response.getStatusCode(), 200); - Factory responseFactory = dto.createDtoFromJson(response.getBody().asString(), Factory.class); - assertTrue(responseFactory.getLinks().contains( - dto.createDto(Link.class).withMethod(HttpMethod.GET).withProduces(MediaType.APPLICATION_JSON) - .withHref(getServerUrl(context) + "/rest/private/factory/" + - CORRECT_FACTORY_ID).withRel("self") - )); - assertTrue(responseFactory.getLinks().contains(expectedCreateProject)); - assertTrue(responseFactory.getLinks() - .contains(dto.createDto(Link.class).withMethod(HttpMethod.GET).withProduces(MediaType.TEXT_PLAIN) - .withHref(getServerUrl(context) + - "/rest/private/analytics/public-metric/factory_used?factory=" + - encode(expectedCreateProject.getHref(), "UTF-8")) - .withRel("accepted"))); - assertTrue(responseFactory.getLinks() - .contains(dto.createDto(Link.class).withMethod(HttpMethod.GET).withProduces(MediaType.TEXT_PLAIN) - .withHref(getServerUrl(context) + "/rest/private/factory/" + - CORRECT_FACTORY_ID + "/snippet?type=url") - .withRel("snippet/url"))); - assertTrue(responseFactory.getLinks() - .contains(dto.createDto(Link.class).withMethod(HttpMethod.GET).withProduces(MediaType.TEXT_PLAIN) - .withHref(getServerUrl(context) + "/rest/private/factory/" + - CORRECT_FACTORY_ID + "/snippet?type=html") - .withRel("snippet/html"))); - assertTrue(responseFactory.getLinks() - .contains(dto.createDto(Link.class).withMethod(HttpMethod.GET).withProduces(MediaType.TEXT_PLAIN) - .withHref(getServerUrl(context) + "/rest/private/factory/" + - CORRECT_FACTORY_ID + "/snippet?type=markdown") - .withRel("snippet/markdown"))); - - - List expectedLinks = new ArrayList<>(8); - expectedLinks.add(expectedCreateProject); - - Link self = dto.createDto(Link.class); - self.setMethod(HttpMethod.GET); - self.setProduces(MediaType.APPLICATION_JSON); - self.setHref(getServerUrl(context) + "/rest/private/factory/" + CORRECT_FACTORY_ID); - self.setRel("self"); - expectedLinks.add(self); - - Link accepted = dto.createDto(Link.class); - accepted.setMethod(HttpMethod.GET); - accepted.setProduces(MediaType.TEXT_PLAIN); - accepted.setHref(getServerUrl(context) + "/rest/private/analytics/public-metric/factory_used?factory=" + - encode(expectedCreateProject.getHref(), "UTF-8")); - accepted.setRel("accepted"); - expectedLinks.add(accepted); - - Link snippetUrl = dto.createDto(Link.class); - snippetUrl.setProduces(MediaType.TEXT_PLAIN); - snippetUrl.setHref(getServerUrl(context) + "/rest/private/factory/" + CORRECT_FACTORY_ID + "/snippet?type=url"); - snippetUrl.setRel("snippet/url"); - snippetUrl.setMethod(HttpMethod.GET); - expectedLinks.add(snippetUrl); - - Link snippetHtml = dto.createDto(Link.class); - snippetHtml.setProduces(MediaType.TEXT_PLAIN); - snippetHtml.setHref(getServerUrl(context) + "/rest/private/factory/" + CORRECT_FACTORY_ID + - "/snippet?type=html"); - snippetHtml.setMethod(HttpMethod.GET); - snippetHtml.setRel("snippet/html"); - expectedLinks.add(snippetHtml); - - Link snippetMarkdown = dto.createDto(Link.class); - snippetMarkdown.setProduces(MediaType.TEXT_PLAIN); - snippetMarkdown.setHref(getServerUrl(context) + "/rest/private/factory/" + CORRECT_FACTORY_ID + - "/snippet?type=markdown"); - snippetMarkdown.setRel("snippet/markdown"); - snippetMarkdown.setMethod(HttpMethod.GET); - expectedLinks.add(snippetMarkdown); - - Link snippetiFrame = dto.createDto(Link.class); - snippetiFrame.setProduces(MediaType.TEXT_PLAIN); - snippetiFrame.setHref(getServerUrl(context) + "/rest/private/factory/" + CORRECT_FACTORY_ID + - "/snippet?type=iframe"); - snippetiFrame.setRel("snippet/iframe"); - snippetiFrame.setMethod(HttpMethod.GET); - expectedLinks.add(snippetiFrame); - - for (Link link : responseFactory.getLinks()) { - //This transposition need because proxy objects doesn't contains equals method. - Link testLink = dto.createDto(Link.class); - testLink.setProduces(link.getProduces()); - testLink.setHref(link.getHref()); - testLink.setRel(link.getRel()); - testLink.setMethod(HttpMethod.GET); - assertTrue(expectedLinks.contains(testLink)); - } - - verify(factoryStore).saveFactory(Matchers.any(), eq(Collections.emptySet())); + final Response response = given().auth() + .basic(ADMIN_USER_NAME, ADMIN_USER_PASSWORD) + .multiPart("factory", DTO.toJson(factoryDto), APPLICATION_JSON) + .multiPart("image", File.createTempFile("img", ".jpeg"), "image/jpeg") + .expect() + .statusCode(200) + .when() + .post(SERVICE_PATH); + final FactoryDto result = getFromResponse(response, FactoryDto.class); + verify(factoryManager).saveFactory(any(FactoryDto.class), anySetOf(FactoryImage.class)); + factoryDto.withLinks(result.getLinks()) + .getCreator() + .withCreated(result.getCreator().getCreated()); + assertEquals(result, factoryDto); } @Test - public void shouldBeAbleToSaveFactoryWithOutImageWithOrgId() throws Exception { - // given - Factory factory = prepareFactoryWithGivenStorage("git", "http://github.com/codenvy/platform-api.git"); + public void shouldThrowBadRequestExceptionWhenInvalidFactorySectionProvided() throws Exception { + doThrow(new JsonSyntaxException("Invalid json")).when(factoryBuilderSpy).build(any(InputStream.class)); + final Response response = given().auth() + .basic(ADMIN_USER_NAME, ADMIN_USER_PASSWORD) + .multiPart("factory", "invalid content", FACTORY_IMAGE_MIME_TYPE) + .expect() + .statusCode(400) + .when() + .post(SERVICE_PATH); - FactorySaveAnswer factorySaveAnswer = new FactorySaveAnswer(); - when(factoryStore.saveFactory(any(Factory.class), anySetOf(FactoryImage.class))).then(factorySaveAnswer); - when(factoryStore.getFactory(CORRECT_FACTORY_ID)).then(factorySaveAnswer); - - // when, then - Response response = - given().auth().basic(JettyHttpServer.ADMIN_USER_NAME, JettyHttpServer.ADMIN_USER_PASSWORD)// - .multiPart("factory", JsonHelper.toJson(factory), MediaType.APPLICATION_JSON).when() - .post("/private" + SERVICE_PATH); - - // then - assertEquals(response.getStatusCode(), 200); + final ServiceError err = getFromResponse(response, ServiceError.class); + assertEquals(err.getMessage(), "Invalid JSON value of the field 'factory' provided"); } @Test - public void shouldBeAbleToSaveFactoryWithSetImageFieldButWithOutImageContent() throws Exception { - // given - Factory factory = prepareFactoryWithGivenStorage("git", "http://github.com/codenvy/platform-api.git"); + public void shouldThrowBadRequestExceptionWhenNoFactorySectionProvided() throws Exception { + final Response response = given().auth() + .basic(ADMIN_USER_NAME, ADMIN_USER_PASSWORD) + .multiPart("some data", "some content", FACTORY_IMAGE_MIME_TYPE) + .expect() + .statusCode(400) + .when() + .post(SERVICE_PATH); - FactorySaveAnswer factorySaveAnswer = new FactorySaveAnswer(); - when(factoryStore.saveFactory(any(Factory.class), anySetOf(FactoryImage.class))).then(factorySaveAnswer); - when(factoryStore.getFactory(CORRECT_FACTORY_ID)).then(factorySaveAnswer); - - // when, then - given().auth().basic(JettyHttpServer.ADMIN_USER_NAME, JettyHttpServer.ADMIN_USER_PASSWORD)// - .multiPart("factory", dto.toJson(factory), MediaType.APPLICATION_JSON)// - .multiPart("image", File.createTempFile("123456", ".jpeg"), "image/jpeg")// - .expect().statusCode(200) - .when().post("/private" + SERVICE_PATH); - - verify(factoryStore).saveFactory(Matchers.any(), eq(Collections.emptySet())); + final ServiceError err = getFromResponse(response, ServiceError.class); + assertEquals(err.getMessage(), "factory configuration required"); } @Test - public void shouldReturnStatus409OnSaveFactoryIfImageHasUnsupportedMediaType() throws Exception { - // given - Factory factory = prepareFactoryWithGivenStorage("git", "http://github.com/codenvy/platform-api.git"); - URL resource = Thread.currentThread().getContextClassLoader().getResource("100x100_image.jpeg"); - assertNotNull(resource); - Path path = Paths.get(resource.toURI()); + public void shouldThrowServerExceptionWhenProvidedFactoryDataInvalid() throws Exception { + final String errMessage = "eof"; + doThrow(new IOException(errMessage)).when(factoryBuilderSpy).build(any(InputStream.class)); + final Response response = given().auth() + .basic(ADMIN_USER_NAME, ADMIN_USER_PASSWORD) + .multiPart("factory", "any content", FACTORY_IMAGE_MIME_TYPE) + .expect() + .statusCode(500) + .when() + .post(SERVICE_PATH); - when(factoryStore.saveFactory(any(Factory.class), anySetOf(FactoryImage.class))).thenReturn(CORRECT_FACTORY_ID); - when(factoryStore.getFactory(CORRECT_FACTORY_ID)).thenReturn(factory); - - // when, then - Response response = given().auth() - .basic(JettyHttpServer.ADMIN_USER_NAME, JettyHttpServer.ADMIN_USER_PASSWORD)// - .multiPart("factory", JsonHelper.toJson(factory), MediaType.APPLICATION_JSON)// - .multiPart("image", path.toFile(), "image/tiff")// - .expect() - .statusCode(409) - .when().post("/private" + SERVICE_PATH); - - assertEquals(dto.createDtoFromJson(response.getBody().asString(), ServiceError.class).getMessage(), - "Image media type 'image/tiff' is unsupported."); + final ServiceError err = getFromResponse(response, ServiceError.class); + assertEquals(err.getMessage(), errMessage); } @Test - public void shouldBeAbleToGetFactory(ITestContext context) throws Exception { - // given - String factoryName = "factoryName"; - Factory factory = dto.createDto(Factory.class); - factory.setId(CORRECT_FACTORY_ID); - factory.setName(factoryName); - factory.setCreator(dto.createDto(Author.class).withUserId(userId)); - URL resource = Thread.currentThread().getContextClassLoader().getResource("100x100_image.jpeg"); - assertNotNull(resource); - Path path = Paths.get(resource.toURI()); - byte[] data = Files.readAllBytes(path); - FactoryImage image1 = new FactoryImage(data, "image/jpeg", "image123456789"); - FactoryImage image2 = new FactoryImage(data, "image/png", "image987654321"); - Set images = new HashSet<>(); - images.add(image1); - images.add(image2); - Link expectedCreateProject = dto.createDto(Link.class); - expectedCreateProject.setProduces("text/html"); - expectedCreateProject.setHref(getServerUrl(context) + "/f?id=" + CORRECT_FACTORY_ID); - expectedCreateProject.setRel("accept"); + public void shouldSaveFactoryWithoutImages() throws Exception { + final Factory factory = createFactory(); + final FactoryDto factoryDto = asDto(factory, user); + when(factoryManager.saveFactory(any(FactoryDto.class))).thenReturn(factory); - when(factoryStore.getFactory(CORRECT_FACTORY_ID)).thenReturn(factory); - when(factoryStore.getFactoryImages(CORRECT_FACTORY_ID, null)).thenReturn(images); + final Response response = given().auth() + .basic(ADMIN_USER_NAME, ADMIN_USER_PASSWORD) + .contentType(ContentType.JSON) + .body(factoryDto) + .expect() + .statusCode(200) + .post(SERVICE_PATH); - // when - Response response = given().when().get(SERVICE_PATH + "/" + CORRECT_FACTORY_ID); - - // then - assertEquals(response.getStatusCode(), 200); - Factory responseFactory = JsonHelper.fromJson(response.getBody().asString(), - Factory.class, null); - - List expectedLinks = new ArrayList<>(10); - expectedLinks.add(expectedCreateProject); - - Link expectedCreateProjectByName = dto.createDto(Link.class); - expectedCreateProjectByName.setProduces("text/html"); - expectedCreateProjectByName.setHref(getServerUrl(context) + "/f?name=" + factoryName + "&user=" + JettyHttpServer.ADMIN_USER_NAME); - expectedCreateProjectByName.setRel("accept-named"); - expectedLinks.add(expectedCreateProjectByName); - - Link self = dto.createDto(Link.class); - self.setProduces(MediaType.APPLICATION_JSON); - self.setHref(getServerUrl(context) + "/rest/factory/" + CORRECT_FACTORY_ID); - self.setRel("self"); - expectedLinks.add(self); - - Link imageJpeg = dto.createDto(Link.class); - imageJpeg.setProduces("image/jpeg"); - imageJpeg.setHref(getServerUrl(context) + "/rest/factory/" + CORRECT_FACTORY_ID + - "/image?imgId=image123456789"); - imageJpeg.setRel("image"); - expectedLinks.add(imageJpeg); - - Link imagePng = dto.createDto(Link.class); - imagePng.setProduces("image/png"); - imagePng.setHref(getServerUrl(context) + "/rest/factory/" + CORRECT_FACTORY_ID + "/image?imgId=image987654321"); - imagePng.setRel("image"); - expectedLinks.add(imagePng); - - Link accepted = dto.createDto(Link.class); - accepted.setProduces(MediaType.TEXT_PLAIN); - accepted.setHref(getServerUrl(context) + "/rest/analytics/public-metric/factory_used?factory=" + - encode(expectedCreateProject.getHref(), "UTF-8")); - accepted.setRel("accepted"); - expectedLinks.add(accepted); - - Link snippetUrl = dto.createDto(Link.class); - snippetUrl.setProduces(MediaType.TEXT_PLAIN); - snippetUrl.setHref(getServerUrl(context) + "/rest/factory/" + CORRECT_FACTORY_ID + "/snippet?type=url"); - snippetUrl.setRel("snippet/url"); - expectedLinks.add(snippetUrl); - - Link snippetHtml = dto.createDto(Link.class); - snippetHtml.setProduces(MediaType.TEXT_PLAIN); - snippetHtml.setHref(getServerUrl(context) + "/rest/factory/" + CORRECT_FACTORY_ID + "/snippet?type=html"); - snippetHtml.setRel("snippet/html"); - expectedLinks.add(snippetHtml); - - Link snippetMarkdown = dto.createDto(Link.class); - snippetMarkdown.setProduces(MediaType.TEXT_PLAIN); - snippetMarkdown.setHref(getServerUrl(context) + "/rest/factory/" + CORRECT_FACTORY_ID + - "/snippet?type=markdown"); - snippetMarkdown.setRel("snippet/markdown"); - expectedLinks.add(snippetMarkdown); - - Link snippetiFrame = dto.createDto(Link.class); - snippetiFrame.setProduces(MediaType.TEXT_PLAIN); - snippetiFrame.setHref(getServerUrl(context) + "/rest/factory/" + CORRECT_FACTORY_ID + - "/snippet?type=iframe"); - snippetiFrame.setRel("snippet/iframe"); - expectedLinks.add(snippetiFrame); - - for (Link link : responseFactory.getLinks()) { - Link testLink = dto.createDto(Link.class); - testLink.setProduces(link.getProduces()); - testLink.setHref(link.getHref()); - testLink.setRel(link.getRel()); - //This transposition need because proxy objects doesn't contains equals method. - assertTrue(expectedLinks.contains(testLink)); - } + assertEquals(getFromResponse(response, FactoryDto.class).withLinks(emptyList()), factoryDto); } @Test - public void shouldReturnStatus404OnGetFactoryWithIllegalId() throws Exception { - // given - doThrow(new NotFoundException(format("Factory with id %s is not found.", ILLEGAL_FACTORY_ID))).when(factoryStore) - .getFactory(anyString()); - - // when, then - Response response = given().expect() - .statusCode(404) - .when() - .get(SERVICE_PATH + "/" + ILLEGAL_FACTORY_ID); - - assertEquals(dto.createDtoFromJson(response.getBody().asString(), ServiceError.class).getMessage(), - format("Factory with id %s is not found.", ILLEGAL_FACTORY_ID)); + public void shouldThrowBadRequestExceptionWhenFactoryConfigurationNotProvided() throws Exception { + final Response response = given().auth() + .basic(ADMIN_USER_NAME, ADMIN_USER_PASSWORD) + .contentType(ContentType.JSON) + .expect() + .statusCode(400) + .post(SERVICE_PATH); + final String errMessage = getFromResponse(response, ServiceError.class).getMessage(); + assertEquals(errMessage, "Factory configuration required"); } @Test - public void shouldBeAbleToGetFactoryImage() throws Exception { - // given - URL resource = Thread.currentThread().getContextClassLoader().getResource("100x100_image.jpeg"); - assertNotNull(resource); - Path path = Paths.get(resource.toURI()); - byte[] imageContent = Files.readAllBytes(path); - FactoryImage image = new FactoryImage(imageContent, "image/jpeg", "imageName"); + public void shouldReturnFactoryByIdentifierWithoutValidation() throws Exception { + final Factory factory = createFactory(); + final FactoryDto factoryDto = asDto(factory, user); + when(factoryManager.getById(FACTORY_ID)).thenReturn(factory); + when(factoryManager.getFactoryImages(FACTORY_ID)).thenReturn(emptySet()); - when(factoryStore.getFactoryImages(CORRECT_FACTORY_ID, null)).thenReturn(new HashSet<>(singletonList(image))); + final Response response = given().when() + .expect() + .statusCode(200) + .get(SERVICE_PATH + "/" + FACTORY_ID); - // when - Response response = given().when().get(SERVICE_PATH + "/" + CORRECT_FACTORY_ID + "/image?imgId=imageName"); - - // then - assertEquals(response.getStatusCode(), 200); - assertEquals(response.getContentType(), "image/jpeg"); - assertEquals(response.getHeader("content-length"), String.valueOf(imageContent.length)); - assertEquals(response.asByteArray(), imageContent); + assertEquals(getFromResponse(response, FactoryDto.class).withLinks(emptyList()), factoryDto); } @Test - public void shouldBeAbleToGetFactoryDefaultImage() throws Exception { - // given - URL resource = Thread.currentThread().getContextClassLoader().getResource("100x100_image.jpeg"); - assertNotNull(resource); - Path path = Paths.get(resource.toURI()); - byte[] imageContent = Files.readAllBytes(path); - FactoryImage image = new FactoryImage(imageContent, "image/jpeg", "imageName"); + public void shouldReturnFactoryByIdentifierWithValidation() throws Exception { + final Factory factory = createFactory(); + final FactoryDto factoryDto = asDto(factory, user); + when(factoryManager.getById(FACTORY_ID)).thenReturn(factory); + when(factoryManager.getFactoryImages(FACTORY_ID)).thenReturn(emptySet()); + doNothing().when(acceptValidator).validateOnAccept(any(FactoryDto.class)); - when(factoryStore.getFactoryImages(CORRECT_FACTORY_ID, null)).thenReturn(new HashSet<>(singletonList(image))); + final Response response = given().when() + .expect() + .statusCode(200) + .get(SERVICE_PATH + "/" + FACTORY_ID + "?validate=true"); - // when - Response response = given().when().get(SERVICE_PATH + "/" + CORRECT_FACTORY_ID + "/image"); - - // then - assertEquals(response.getStatusCode(), 200); - assertEquals(response.getContentType(), "image/jpeg"); - assertEquals(response.getHeader("content-length"), String.valueOf(imageContent.length)); - assertEquals(response.asByteArray(), imageContent); + assertEquals(getFromResponse(response, FactoryDto.class).withLinks(emptyList()), factoryDto); } @Test - public void shouldReturnStatus404OnGetFactoryImageWithIllegalId() throws Exception { - // given - when(factoryStore.getFactoryImages(CORRECT_FACTORY_ID, null)).thenReturn(new HashSet<>()); + public void shouldThrowNotFoundExceptionWhenFactoryIsNotExist() throws Exception { + final String errMessage = format("Factory with id %s is not found", FACTORY_ID); + doThrow(new NotFoundException(errMessage)).when(factoryManager) + .getById(anyString()); - // when, then - Response response = given().expect() - .statusCode(404) - .when() - .get(SERVICE_PATH + "/" + CORRECT_FACTORY_ID + "/image?imgId=illegalImageId"); + final Response response = given().expect() + .statusCode(404) + .when() + .get(SERVICE_PATH + "/" + FACTORY_ID); - assertEquals(dto.createDtoFromJson(response.getBody().asString(), ServiceError.class).getMessage(), - format("Image with id %s is not found.", "illegalImageId")); + assertEquals(getFromResponse(response, ServiceError.class).getMessage(), errMessage); } @Test - public void shouldResponse404OnGetImageIfFactoryDoesNotExist() throws Exception { - // given - doThrow(new NotFoundException(format("Factory with id %s is not found.", ILLEGAL_FACTORY_ID))).when(factoryStore) - .getFactoryImages(anyString(), - anyString()); + public void shouldReturnFactoryListByNameAttribute() throws Exception { + final Factory factory = createFactory(); + when(factoryManager.getByAttribute(1, 0, ImmutableList.of(Pair.of("factory.name", factory.getName())))) + .thenReturn(ImmutableList.of(factory)); - // when, then - Response response = given().expect() - .statusCode(404) - .when() - .get(SERVICE_PATH + "/" + ILLEGAL_FACTORY_ID + "/image?imgId=ImageId"); + final Response response = given().auth() + .basic(ADMIN_USER_NAME, ADMIN_USER_PASSWORD) + .contentType(APPLICATION_JSON) + .when() + .expect() + .statusCode(200) + .get(SERVICE_PATH + "/find?maxItems=1&skipCount=0&factory.name=" + factory.getName()); - assertEquals(dto.createDtoFromJson(response.getBody().asString(), ServiceError.class).getMessage(), - format("Factory with id %s is not found.", ILLEGAL_FACTORY_ID)); + final List res = unwrapDtoList(response, FactoryDto.class); + assertEquals(res.size(), 1); + assertEquals(res.get(0).withLinks(emptyList()), asDto(factory, user)); } @Test - public void shouldBeAbleToReturnUrlSnippet(ITestContext context) throws Exception { - // given - when(factoryStore.getFactory(CORRECT_FACTORY_ID)).thenReturn(dto.createDto - (Factory.class)); + public void shouldReturnFactoryListByCreatorAttribute() throws Exception { + final Factory factory1 = createNamedFactory("factory1"); + final Factory factory2 = createNamedFactory("factory2"); + when(factoryManager.getByAttribute(2, 0, ImmutableList.of(Pair.of("factory.creator.name", user.getName())))) + .thenReturn(ImmutableList.of(factory1, factory2)); - // when, then - given().expect() - .statusCode(200) - .contentType(MediaType.TEXT_PLAIN) - .body(equalTo(getServerUrl(context) + "/factory?id=" + CORRECT_FACTORY_ID)) - .when() - .get(SERVICE_PATH + "/" + CORRECT_FACTORY_ID + "/snippet?type=url"); + final Response response = given().auth() + .basic(ADMIN_USER_NAME, ADMIN_USER_PASSWORD) + .contentType(APPLICATION_JSON) + .when() + .expect() + .statusCode(200) + .get(SERVICE_PATH + "/find?maxItems=2&skipCount=0&factory.creator.name=" + user.getName()); + + final Set res = unwrapDtoList(response, FactoryDto.class).stream() + .map(f -> f.withLinks(emptyList())) + .collect(toSet()); + assertEquals(res.size(), 2); + assertTrue(res.containsAll(ImmutableList.of(asDto(factory1, user), asDto(factory2, user)))); } @Test - public void shouldBeAbleToReturnUrlSnippetIfTypeIsNotSet(ITestContext context) throws Exception { - // given - when(factoryStore.getFactory(CORRECT_FACTORY_ID)).thenReturn(dto.createDto - (Factory.class)); + public void shouldThrowBadRequestWhenGettingFactoryByEmptyAttributeList() throws Exception { + final Response response = given().auth() + .basic(ADMIN_USER_NAME, ADMIN_USER_PASSWORD) + .contentType(APPLICATION_JSON) + .when() + .expect() + .get(SERVICE_PATH + "/find?maxItems=1&skipCount=0"); - // when, then - given().expect() - .statusCode(200) - .contentType(MediaType.TEXT_PLAIN) - .body(equalTo(getServerUrl(context) + "/factory?id=" + CORRECT_FACTORY_ID)) - .when() - .get(SERVICE_PATH + "/" + CORRECT_FACTORY_ID + "/snippet"); + assertEquals(response.getStatusCode(), 400); + assertEquals(DTO.createDtoFromJson(response.getBody().asString(), ServiceError.class) + .getMessage(), "Query must contain at least one attribute"); } - @Test - public void shouldBeAbleToReturnHtmlSnippet(ITestContext context) throws Exception { - // given - when(factoryStore.getFactory(CORRECT_FACTORY_ID)).thenReturn(dto.createDto(Factory.class)); - - // when, then - Response response = given().expect() - .statusCode(200) - .contentType(MediaType.TEXT_PLAIN) - .when() - .get(SERVICE_PATH + "/" + CORRECT_FACTORY_ID + "/snippet?type=html"); - - assertEquals(response.body().asString(), ""); - } - - @Test - public void shouldBeAbleToReturnMarkdownSnippetForFactory1WithImage(ITestContext context) throws Exception { - // given - SourceStorageDto storageDto = dto.createDto(SourceStorageDto.class) - .withType("git") - .withLocation("http://github.com/codenvy/platform-api.git"); - Factory factory = dto.createDto(Factory.class) - .withV("4.0") - .withWorkspace(dto.createDto(WorkspaceConfigDto.class) - .withProjects(singletonList(dto.createDto(ProjectConfigDto.class) - .withSource(storageDto)))) - .withId(CORRECT_FACTORY_ID) - .withButton(dto.createDto(Button.class) - .withType(Button.ButtonType.logo)); - String imageName = "1241234"; - FactoryImage image = new FactoryImage(); - image.setName(imageName); - - when(factoryStore.getFactory(CORRECT_FACTORY_ID)).thenReturn(factory); - when(factoryStore.getFactoryImages(CORRECT_FACTORY_ID, null)).thenReturn(new HashSet<>(singletonList(image))); - // when, then - given().expect() - .statusCode(200) - .contentType(MediaType.TEXT_PLAIN) - .body( - equalTo("[![alt](" + getServerUrl(context) + "/api/factory/" + CORRECT_FACTORY_ID + "/image?imgId=" + - imageName + ")](" + - getServerUrl(context) + "/factory?id=" + - CORRECT_FACTORY_ID + ")") - ) - .when() - .get(SERVICE_PATH + "/" + CORRECT_FACTORY_ID + "/snippet?type=markdown"); - } - - - @Test - public void shouldBeAbleToReturnMarkdownSnippetForFactory2WithImage(ITestContext context) throws Exception { - // given - String imageName = "1241234"; - Factory factory = dto.createDto(Factory.class); - factory.setId(CORRECT_FACTORY_ID); - factory.setV("2.0"); - factory.setButton(dto.createDto(Button.class).withType(Button.ButtonType.logo)); - - FactoryImage image = new FactoryImage(); - image.setName(imageName); - - when(factoryStore.getFactory(CORRECT_FACTORY_ID)).thenReturn(factory); - when(factoryStore.getFactoryImages(CORRECT_FACTORY_ID, null)).thenReturn(new HashSet<>(singletonList(image))); - // when, then - given().expect() - .statusCode(200) - .contentType(MediaType.TEXT_PLAIN) - .body( - equalTo("[![alt](" + getServerUrl(context) + "/api/factory/" + CORRECT_FACTORY_ID + "/image?imgId=" + - imageName + ")](" + - getServerUrl(context) + "/factory?id=" + - CORRECT_FACTORY_ID + ")") - ) - .when() - .get(SERVICE_PATH + "/" + CORRECT_FACTORY_ID + "/snippet?type=markdown"); - } - - @Test - public void shouldBeAbleToReturnMarkdownSnippetForFactory1WithoutImage(ITestContext context) throws Exception { - // given - Factory factory = prepareFactoryWithGivenStorage("git", "http://github.com/codenvy/platform-api.git") - .withId(CORRECT_FACTORY_ID) - .withButton(dto.createDto(Button.class) - .withType(Button.ButtonType.nologo) - .withAttributes(dto.createDto(ButtonAttributes.class) - .withColor("white"))); - - when(factoryStore.getFactory(CORRECT_FACTORY_ID)).thenReturn(factory); - // when, then - given().expect() - .statusCode(200) - .contentType(MediaType.TEXT_PLAIN) - .body( - equalTo("[![alt](" + getServerUrl(context) + "/factory/resources/factory-white.png)](" + getServerUrl - (context) + - "/factory?id=" + - CORRECT_FACTORY_ID + ")") - ) - .when() - .get(SERVICE_PATH + "/" + CORRECT_FACTORY_ID + "/snippet?type=markdown"); - } - - @Test - public void shouldNotBeAbleToGetMarkdownSnippetForFactory1WithoutStyle() throws Exception { - // given - Factory factory = prepareFactoryWithGivenStorage("git", "http://github.com/codenvy/platform-api.git").withId(CORRECT_FACTORY_ID); - when(factoryStore.getFactory(CORRECT_FACTORY_ID)).thenReturn(factory); - // when, then - Response response = given().expect() - .statusCode(400) - .when() - .get(SERVICE_PATH + "/" + CORRECT_FACTORY_ID + "/snippet?type=markdown"); - - assertEquals(dto.createDtoFromJson(response.getBody().asInputStream(), ServiceError.class).getMessage(), - "Unable to generate markdown snippet for factory without button"); - } - - @Test - public void shouldNotBeAbleToGetMarkdownSnippetForFactory2WithoutColor() throws Exception { - // given - Factory factory = prepareFactoryWithGivenStorage("git", "http://github.com/codenvy/platform-api.git") - .withId(CORRECT_FACTORY_ID) - .withButton(dto.createDto(Button.class) - .withType(Button.ButtonType.nologo) - .withAttributes(dto.createDto(ButtonAttributes.class) - .withColor(null))); - - when(factoryStore.getFactory(CORRECT_FACTORY_ID)).thenReturn(factory); - // when, then - Response response = given().expect() - .statusCode(400) - .when() - .get(SERVICE_PATH + "/" + CORRECT_FACTORY_ID + "/snippet?type=markdown"); - - assertEquals(dto.createDtoFromJson(response.getBody().asInputStream(), ServiceError.class).getMessage(), - "Unable to generate markdown snippet with nologo button and empty color"); - } - - @Test - public void shouldResponse404OnGetSnippetIfFactoryDoesNotExist() throws Exception { - // given - doThrow(new NotFoundException("Factory URL with id " + ILLEGAL_FACTORY_ID + " is not found.")).when(factoryStore) - .getFactory(anyString()); - - // when, then - Response response = given().expect() - .statusCode(404) - .when() - .get(SERVICE_PATH + "/" + ILLEGAL_FACTORY_ID + "/snippet?type=url"); - - assertEquals(dto.createDtoFromJson(response.getBody().asString(), ServiceError.class).getMessage(), - "Factory URL with id " + ILLEGAL_FACTORY_ID + " is not found."); - } - - - /** - * Checks that the user can remove an existing factory - */ - @Test - public void shouldBeAbleToRemoveAFactory() throws Exception { - // given - Factory factory = prepareFactoryWithGivenStorage("git", "http://github.com/codenvy/platform-api.git"); - when(factoryStore.getFactory(CORRECT_FACTORY_ID)).thenReturn(factory); - - // when, then - Response response = given().auth() - .basic(JettyHttpServer.ADMIN_USER_NAME, JettyHttpServer.ADMIN_USER_PASSWORD) - .param("id", CORRECT_FACTORY_ID) - .when() - .delete("/private" + SERVICE_PATH + "/" + CORRECT_FACTORY_ID); - - assertEquals(response.getStatusCode(), 204); - - // check there was a call on the remove operation with expected ID - verify(factoryStore).removeFactory(CORRECT_FACTORY_ID); - } - - /** - * Checks that the user can not remove an unknown factory - */ - @Test - public void shouldNotBeAbleToRemoveNotExistingFactory() throws Exception { - doThrow(new NotFoundException("Not found")).when(factoryStore).removeFactory(anyString()); - - // when, then - Response response = given().auth() - .basic(JettyHttpServer.ADMIN_USER_NAME, JettyHttpServer.ADMIN_USER_PASSWORD) - .param("id", ILLEGAL_FACTORY_ID) - .when() - .delete("/private" + SERVICE_PATH + "/" + ILLEGAL_FACTORY_ID); - - assertEquals(response.getStatusCode(), 404); - } - - /** - * Checks that the user can update an existing factory - */ @Test public void shouldBeAbleToUpdateFactory() throws Exception { + final Factory existed = createFactory(); + final Factory update = createFactoryWithStorage(null, "git", "http://github.com/codenvy/platform-api1.git"); + when(factoryManager.getById(FACTORY_ID)).thenReturn(existed); + when(factoryManager.updateFactory(any())).thenReturn(update); - // given - Factory beforeFactory = prepareFactoryWithGivenStorage("git", "http://github.com/codenvy/platform-api.git") - .withCreator(dto.createDto(Author.class).withCreated(System.currentTimeMillis())); - beforeFactory.setId(CORRECT_FACTORY_ID); - Factory afterFactory = prepareFactoryWithGivenStorage("git", "http://github.com/codenvy/platform-api2.git"); + final Response response = given().auth() + .basic(ADMIN_USER_NAME, ADMIN_USER_PASSWORD) + .contentType(APPLICATION_JSON) + .body(JsonHelper.toJson(asDto(existed, user))) + .when() + .expect() + .statusCode(200) + .put(SERVICE_PATH + "/" + FACTORY_ID); - when(factoryStore.getFactory(CORRECT_FACTORY_ID)).thenReturn(beforeFactory); - - // when, then - Response response = given().auth() - .basic(JettyHttpServer.ADMIN_USER_NAME, JettyHttpServer.ADMIN_USER_PASSWORD) - .contentType(MediaType.APPLICATION_JSON) - .body(JsonHelper.toJson(afterFactory)) - .when() - .put("/private" + SERVICE_PATH + "/" + CORRECT_FACTORY_ID); - - assertEquals(response.getStatusCode(), 200); - - Factory responseFactory = dto.createDtoFromJson(response.getBody().asInputStream(), Factory.class); - assertEquals(responseFactory.getWorkspace(), afterFactory.getWorkspace()); - - - // check there was a call on the update operation with expected ID - verify(factoryStore).updateFactory(eq(CORRECT_FACTORY_ID), any(Factory.class)); + final FactoryDto result = getFromResponse(response, FactoryDto.class); + verify(factoryManager, times(1)).updateFactory(any()); + assertEquals(result.withLinks(emptyList()), asDto(update, user)); } - /** - * Checks that the user can not update an unknown existing factory - */ @Test - public void shouldNotBeAbleToUpdateAnUnknownFactory() throws Exception { - // given - Factory factory = prepareFactoryWithGivenStorage("git", "http://github.com/codenvy/platform-api.git"); - doThrow(new NotFoundException(format("Factory with id %s is not found.", ILLEGAL_FACTORY_ID))).when(factoryStore) - .getFactory(anyString()); + public void shouldThrowNotFoundExceptionWhenUpdatingNonExistingFactory() throws Exception { + final Factory factory = createFactoryWithStorage(FACTORY_NAME, + "git", + "http://github.com/codenvy/platform-api.git"); + doThrow(new NotFoundException(format("Factory with id %s is not found.", FACTORY_ID))).when(factoryManager) + .getById(anyString()); - // when, then - Response response = given().auth().basic(JettyHttpServer.ADMIN_USER_NAME, JettyHttpServer.ADMIN_USER_PASSWORD) - .contentType(MediaType.APPLICATION_JSON) - .body(JsonHelper.toJson(factory)) - .when() - .put("/private" + SERVICE_PATH + "/" + ILLEGAL_FACTORY_ID); + final Response response = given().auth().basic(ADMIN_USER_NAME, ADMIN_USER_PASSWORD) + .contentType(APPLICATION_JSON) + .body(JsonHelper.toJson(factory)) + .when() + .put(SERVICE_PATH + "/" + FACTORY_ID); assertEquals(response.getStatusCode(), 404); - assertEquals(dto.createDtoFromJson(response.getBody().asString(), ServiceError.class).getMessage(), - format("Factory with id %s is not found.", ILLEGAL_FACTORY_ID)); + assertEquals(DTO.createDtoFromJson(response.getBody().asString(), ServiceError.class).getMessage(), + format("Factory with id %s is not found.", FACTORY_ID)); } - /** - * Checks that the user can not update a factory with a null one - */ @Test public void shouldNotBeAbleToUpdateANullFactory() throws Exception { + final Response response = given().auth() + .basic(ADMIN_USER_NAME, ADMIN_USER_PASSWORD) + .contentType(APPLICATION_JSON) + .when() + .put(SERVICE_PATH + "/" + FACTORY_ID); - // when, then - Response response = given().auth() - .basic(JettyHttpServer.ADMIN_USER_NAME, JettyHttpServer.ADMIN_USER_PASSWORD) - .contentType(MediaType.APPLICATION_JSON) - .when() - .put("/private" + SERVICE_PATH + "/" + ILLEGAL_FACTORY_ID); - - assertEquals(response.getStatusCode(), BAD_REQUEST.getStatusCode()); - assertEquals(dto.createDtoFromJson(response.getBody().asString(), ServiceError.class).getMessage(), - "The factory information is not updateable"); - - } - - @Test(dataProvider = "badSnippetTypeProvider") - public void shouldResponse409OnGetSnippetIfTypeIsIllegal(String type) throws Exception { - // given - when(factoryStore.getFactory(CORRECT_FACTORY_ID)).thenReturn(dto.createDto(Factory.class)); - - // when, then - Response response = given().expect() - .statusCode(BAD_REQUEST.getStatusCode()) - .when() - .get(SERVICE_PATH + "/" + CORRECT_FACTORY_ID + "/snippet?type=" + type); - - assertEquals(dto.createDtoFromJson(response.getBody().asString(), ServiceError.class).getMessage(), - format("Snippet type \"%s\" is unsupported.", type)); - } - - @DataProvider(name = "badSnippetTypeProvider") - public Object[][] badSnippetTypeProvider() { - return new String[][]{{""}, - {null}, - {"mark"}}; - } - - private String getServerUrl(ITestContext context) { - String serverPort = String.valueOf(context.getAttribute(EverrestJetty.JETTY_PORT)); - return "http://localhost:" + serverPort; - } - - - @Test - public void shouldNotFindWhenNoAttributesProvided() throws Exception { - // when - Response response = given().auth() - .basic(JettyHttpServer.ADMIN_USER_NAME, JettyHttpServer.ADMIN_USER_PASSWORD) - .when() - .get("/private" + SERVICE_PATH + "/find"); - // then - assertEquals(response.getStatusCode(), BAD_REQUEST.getStatusCode()); + assertEquals(response.getStatusCode(), 400); + assertEquals(DTO.createDtoFromJson(response.getBody().asString(), ServiceError.class) + .getMessage(), "Factory configuration required"); } @Test - public void shouldFindByAttribute() throws Exception { - // given - Factory factory = prepareFactoryWithGivenStorage("git", "http://github.com/codenvy/platform-api.git") - .withId(CORRECT_FACTORY_ID) - .withCreator(dto.createDto(Author.class).withUserId("uid-123")); + public void shouldRemoveFactoryByGivenIdentifier() throws Exception { + final Factory factory = createFactory(); + when(factoryManager.getById(FACTORY_ID)).thenReturn(factory); - List> expected = singletonList(Pair.of("creator.userid", "uid-123")); - when(factoryStore.findByAttribute(anyInt(), anyInt(), eq(expected))).thenReturn( - Arrays.asList(factory, factory)); + given().auth() + .basic(ADMIN_USER_NAME, ADMIN_USER_PASSWORD) + .param("id", FACTORY_ID) + .expect() + .statusCode(204) + .when() + .delete(SERVICE_PATH + "/" + FACTORY_ID); - // when - Response response = given().auth() - .basic(JettyHttpServer.ADMIN_USER_NAME, JettyHttpServer.ADMIN_USER_PASSWORD) - .when() - .get("/private" + SERVICE_PATH + "/find?creator.userid=uid-123"); - - // then - assertEquals(response.getStatusCode(), 200); - List responseFactories = dto.createListDtoFromJson(response.getBody().asString(), Factory.class); - assertEquals(responseFactories.size(), 2); + verify(factoryManager).removeFactory(FACTORY_ID); } - @Test - public void shouldGenerateFactoryJsonIncludeAllProjects() throws Exception { - // given - final String wsId = "workspace123234"; - WorkspaceImpl.WorkspaceImplBuilder userWs = WorkspaceImpl.builder(); - WorkspaceConfigImpl.WorkspaceConfigImplBuilder wsConfig = WorkspaceConfigImpl.builder(); + public void shouldNotThrowAnyExceptionWhenRemovingNonExistingFactory() throws Exception { + doNothing().when(factoryManager).removeFactory(anyString()); - wsConfig.setProjects(Arrays.asList(dto.createDto(ProjectConfigDto.class) - .withSource(dto.createDto(SourceStorageDto.class) - .withType("git") - .withLocation("location")) - .withPath("path"), - dto.createDto(ProjectConfigDto.class) - .withSource(dto.createDto(SourceStorageDto.class) - .withType("git") - .withLocation("location")) - .withPath("path"))); - wsConfig.setName("wsname"); - wsConfig.setDefaultEnv("env1"); - wsConfig.setEnvironments(singletonMap("env1", dto.createDto(EnvironmentDto.class))); - wsConfig.setCommands(singletonList(dto.createDto(CommandDto.class) - .withName("MCI") - .withType("mvn") - .withCommandLine("clean install"))); - userWs.setId(wsId); - userWs.setNamespace("id-2314"); - userWs.setStatus(WorkspaceStatus.RUNNING); - userWs.setConfig(wsConfig.build()); - - WorkspaceImpl usersWorkspace = userWs.build(); - when(workspaceManager.getWorkspace(eq(wsId))).thenReturn(usersWorkspace); - - // when Response response = given().auth() - .basic(JettyHttpServer.ADMIN_USER_NAME, JettyHttpServer.ADMIN_USER_PASSWORD) + .basic(ADMIN_USER_NAME, ADMIN_USER_PASSWORD) + .param("id", FACTORY_ID) .when() - .get("/private" + SERVICE_PATH + "/workspace/" + wsId); + .delete(SERVICE_PATH + "/" + FACTORY_ID); - // then - assertEquals(response.getStatusCode(), 200); - Factory result = dto.createDtoFromJson(response.getBody().asString(), Factory.class); - assertEquals(result.getWorkspace().getProjects().size(), 2); - assertEquals(result.getWorkspace().getName(), usersWorkspace.getConfig().getName()); - assertEquals(result.getWorkspace() - .getEnvironments() - .toString(), - usersWorkspace.getConfig() - .getEnvironments() - .entrySet() - .stream() - .collect(Collectors.toMap(Map.Entry::getKey, entry -> asDto(entry.getValue()))) - .toString()); - assertEquals(result.getWorkspace().getCommands().get(0), asDto(usersWorkspace.getConfig().getCommands().get(0))); + assertEquals(response.getStatusCode(), 204); } @Test @@ -1056,22 +536,21 @@ public class FactoryServiceTest { WorkspaceImpl.WorkspaceImplBuilder ws = WorkspaceImpl.builder(); WorkspaceConfigImpl.WorkspaceConfigImplBuilder wsConfig = WorkspaceConfigImpl.builder(); ws.setId(wsId); - wsConfig.setProjects(Arrays.asList(dto.createDto(ProjectConfigDto.class) - .withPath("/proj1") - .withSource(dto.createDto(SourceStorageDto.class) - .withType("git") - .withLocation("location")), - dto.createDto(ProjectConfigDto.class) - .withPath("/proj2") - .withSource(dto.createDto(SourceStorageDto.class) - .withType("git") - .withLocation("location")))); + wsConfig.setProjects(Arrays.asList(DTO.createDto(ProjectConfigDto.class) + .withPath("/proj1") + .withSource(DTO.createDto(SourceStorageDto.class) + .withType("git") + .withLocation("location")), + DTO.createDto(ProjectConfigDto.class) + .withPath("/proj2") + .withSource(DTO.createDto(SourceStorageDto.class) + .withType("git") + .withLocation("location")))); wsConfig.setName("wsname"); - ws.setNamespace("id-2314"); - wsConfig.setEnvironments(singletonMap("env1", dto.createDto(EnvironmentDto.class))); + wsConfig.setEnvironments(singletonMap("env1", DTO.createDto(EnvironmentDto.class))); wsConfig.setDefaultEnv("env1"); ws.setStatus(WorkspaceStatus.RUNNING); - wsConfig.setCommands(singletonList(dto.createDto(CommandDto.class) + wsConfig.setCommands(singletonList(DTO.createDto(CommandDto.class) .withName("MCI") .withType("mvn") .withCommandLine("clean install"))); @@ -1081,158 +560,72 @@ public class FactoryServiceTest { // when Response response = given().auth() - .basic(JettyHttpServer.ADMIN_USER_NAME, JettyHttpServer.ADMIN_USER_PASSWORD) + .basic(ADMIN_USER_NAME, ADMIN_USER_PASSWORD) .when() .get("/private" + SERVICE_PATH + "/workspace/" + wsId); // then assertEquals(response.getStatusCode(), 200); - Factory result = dto.createDtoFromJson(response.getBody().asString(), Factory.class); + FactoryDto result = DTO.createDtoFromJson(response.getBody().asString(), FactoryDto.class); assertEquals(result.getWorkspace().getProjects().size(), 2); } @Test - public void shouldThrowServerExceptionDuringSaveFactory() throws Exception { - // given - Factory factory = prepareFactoryWithGivenStorage("git", "http://github.com/codenvy/che-core.git"); - URL resource = Thread.currentThread().getContextClassLoader().getResource("100x100_image.jpeg"); - assertNotNull(resource); - Path path = Paths.get(resource.toURI()); - doThrow(IOException.class).when(factoryStore).saveFactory(any(Factory.class), anySetOf(FactoryImage.class)); + public void shouldReturnFactoryImageWithGivenName() throws Exception { + final byte[] imageContent = Files.readAllBytes(getImagePath()); + final FactoryImage image = new FactoryImage(imageContent, FACTORY_IMAGE_MIME_TYPE, IMAGE_NAME); + when(factoryManager.getFactoryImages(FACTORY_ID, IMAGE_NAME)).thenReturn(ImmutableSet.of(image)); - // when, then - Response response = given().auth() - .basic(JettyHttpServer.ADMIN_USER_NAME, JettyHttpServer.ADMIN_USER_PASSWORD) - .multiPart("factory", JsonHelper.toJson(factory), MediaType.APPLICATION_JSON) - .multiPart("image", path.toFile(), "image/jpeg") - .when() - .post("/private" + SERVICE_PATH); - assertEquals(response.getStatusCode(), Status.INTERNAL_SERVER_ERROR.getStatusCode()); + final Response response = given().when() + .expect() + .statusCode(200) + .get(SERVICE_PATH + "/" + FACTORY_ID + "/image?imgId=" + IMAGE_NAME); + + assertEquals(response.getContentType(), FACTORY_IMAGE_MIME_TYPE); + assertEquals(response.getHeader("content-length"), String.valueOf(imageContent.length)); + assertEquals(response.asByteArray(), imageContent); } @Test - public void shouldThrowBadRequestExceptionWhenInvalidFactoryPost() throws Exception { - // given - URL resource = Thread.currentThread().getContextClassLoader().getResource("100x100_image.jpeg"); - assertNotNull(resource); - Path path = Paths.get(resource.toURI()); + public void shouldReturnFirstFoundFactoryImageWhenImageNameNotSpecified() throws Exception { + final byte[] imageContent = Files.readAllBytes(getImagePath()); + final FactoryImage image = new FactoryImage(imageContent, FACTORY_IMAGE_MIME_TYPE, IMAGE_NAME); + when(factoryManager.getFactoryImages(FACTORY_ID)).thenReturn(ImmutableSet.of(image)); - // when, then - Response response = given().auth() - .basic(JettyHttpServer.ADMIN_USER_NAME, JettyHttpServer.ADMIN_USER_PASSWORD) - .multiPart("factory", "invalid factory", MediaType.APPLICATION_JSON) - .multiPart("image", path.toFile(), "image/jpeg") - .when() - .post("/private" + SERVICE_PATH); - assertEquals(response.getStatusCode(), BAD_REQUEST.getStatusCode()); + final Response response = given().when() + .expect() + .statusCode(200) + .get(SERVICE_PATH + "/" + FACTORY_ID + "/image"); + + assertEquals(response.getContentType(), "image/jpeg"); + assertEquals(response.getHeader("content-length"), String.valueOf(imageContent.length)); + assertEquals(response.asByteArray(), imageContent); } @Test - public void shouldSaveFactoryWithoutImages() throws Exception { - // given - Factory factory = prepareFactoryWithGivenStorage("git", "http://github.com/codenvy/che-core.git"); - factory.withCreator(dto.createDto(Author.class).withName("username")); - FactorySaveAnswer factorySaveAnswer = new FactorySaveAnswer(); + public void shouldThrowNotFoundExceptionWhenFactoryImageWithGivenIdentifierIsNotExist() throws Exception { + final String errMessage = "Image with name " + IMAGE_NAME + " is not found"; + when(factoryManager.getFactoryImages(FACTORY_ID, IMAGE_NAME)).thenThrow(new NotFoundException(errMessage)); - when(factoryStore.saveFactory(any(Factory.class), anySetOf(FactoryImage.class))).then(factorySaveAnswer); - when(factoryStore.getFactory(CORRECT_FACTORY_ID)).then(factorySaveAnswer); + final Response response = given().expect() + .statusCode(404) + .when() + .get(SERVICE_PATH + "/" + FACTORY_ID + "/image?imgId=" + IMAGE_NAME); - // when, then - Response response = given().auth() - .basic(JettyHttpServer.ADMIN_USER_NAME, JettyHttpServer.ADMIN_USER_PASSWORD) - .contentType(ContentType.JSON) - .body(factory) - .post("/private" + SERVICE_PATH); - assertEquals(response.getStatusCode(), Status.OK.getStatusCode()); + assertEquals(getFromResponse(response, ServiceError.class).getMessage(), errMessage); } @Test - public void shouldThrowBadRequestExceptionWhenTriedToStoreInvalidFactory() throws Exception { - // given - Factory factory = prepareFactoryWithGivenStorage("git", "http://github.com/codenvy/che-core.git"); - factory.withCreator(dto.createDto(Author.class).withName("username")); - - // when, then - Response response = given().auth() - .basic(JettyHttpServer.ADMIN_USER_NAME, JettyHttpServer.ADMIN_USER_PASSWORD) - .contentType(ContentType.JSON) - .body("") - .post("/private" + SERVICE_PATH); - assertEquals(response.getStatusCode(), BAD_REQUEST.getStatusCode()); - } - - @Test - public void shouldThrowServerExceptionWhenImpossibleCreateLinksForSavedFactory() throws Exception { - // given - Factory factory = prepareFactoryWithGivenStorage("git", "http://github.com/codenvy/che-core.git"); - factory.withCreator(dto.createDto(Author.class).withName("username")); - doThrow(UnsupportedEncodingException.class).when(factoryStore).getFactory(anyString()); - - // when, then - Response response = given().auth() - .basic(JettyHttpServer.ADMIN_USER_NAME, JettyHttpServer.ADMIN_USER_PASSWORD) - .contentType(ContentType.JSON) - .body("{}") - .post("/private" + SERVICE_PATH); - assertEquals(response.getStatusCode(), Status.INTERNAL_SERVER_ERROR.getStatusCode()); - } - - @Test - public void shouldThrowExceptionDuringGetFactory() throws Exception { - // given - doThrow(UnsupportedEncodingException.class).when(factoryStore).getFactoryImages(anyString(), anyString()); - - // when, then - Response response = given().auth() - .basic(JettyHttpServer.ADMIN_USER_NAME, JettyHttpServer.ADMIN_USER_PASSWORD) - .get("/private" + SERVICE_PATH + "/factoryId"); - assertEquals(response.getStatusCode(), Status.INTERNAL_SERVER_ERROR.getStatusCode()); - } - - @Test - public void shouldGetFactoryAndValidateItOnAccept() throws Exception { - // given - Factory factory = dto.createDto(Factory.class) - .withCreator(dto.createDto(Author.class) - .withName(JettyHttpServer.ADMIN_USER_NAME) - .withUserId(userId)) - .withId(CORRECT_FACTORY_ID); - when(factoryStore.getFactory(CORRECT_FACTORY_ID)).thenReturn(factory); - doNothing().when(acceptValidator).validateOnAccept(factory); - - // when, then - Response response = given().auth() - .basic(JettyHttpServer.ADMIN_USER_NAME, JettyHttpServer.ADMIN_USER_PASSWORD) - .get("/private" + SERVICE_PATH + '/' + CORRECT_FACTORY_ID + "?validate=true"); - assertEquals(response.getStatusCode(), Status.OK.getStatusCode()); - } - - @Test - public void should() throws Exception { - // given - Factory factory = dto.createDto(Factory.class) - .withCreator(dto.createDto(Author.class) - .withName(JettyHttpServer.ADMIN_USER_NAME) - .withUserId(userId)) - .withId(CORRECT_FACTORY_ID); - factory.setId(CORRECT_FACTORY_ID); - - Factory storedFactory = dto.createDto(Factory.class) - .withId(CORRECT_FACTORY_ID) - .withCreator(dto.createDto(Author.class).withCreated(10L)); - when(factoryStore.getFactory(anyString())).thenReturn(storedFactory); - doThrow(UnsupportedEncodingException.class).when(factoryStore).getFactoryImages(anyString(), anyString()); - - // when, then - Response response = given().auth() - .basic(JettyHttpServer.ADMIN_USER_NAME, JettyHttpServer.ADMIN_USER_PASSWORD) - .contentType(MediaType.APPLICATION_JSON) - .body(JsonHelper.toJson(factory)) - .when() - .put("/private" + SERVICE_PATH + "/" + CORRECT_FACTORY_ID); - - assertEquals(response.getStatusCode(), Status.INTERNAL_SERVER_ERROR.getStatusCode()); + public void shouldBeAbleToReturnUrlSnippet() throws Exception { + final String result = "snippet"; + when(factoryManager.getFactorySnippet(anyString(), anyString(), any(URI.class))).thenReturn(result); + given().expect() + .statusCode(200) + .contentType(TEXT_PLAIN) + .body(equalTo(result)) + .when() + .get(SERVICE_PATH + "/" + FACTORY_ID + "/snippet?type=url"); } @Test @@ -1242,17 +635,16 @@ public class FactoryServiceTest { WorkspaceImpl.WorkspaceImplBuilder ws = WorkspaceImpl.builder(); WorkspaceConfigImpl.WorkspaceConfigImplBuilder wsConfig = WorkspaceConfigImpl.builder(); ws.setId(wsId); - wsConfig.setProjects(Arrays.asList(dto.createDto(ProjectConfigDto.class) - .withPath("/proj1"), - dto.createDto(ProjectConfigDto.class) - .withPath("/proj2"))); + wsConfig.setProjects(Arrays.asList(DTO.createDto(ProjectConfigDto.class) + .withPath("/proj1"), + DTO.createDto(ProjectConfigDto.class) + .withPath("/proj2"))); wsConfig.setName("wsname"); - ws.setNamespace("id-2314"); - wsConfig.setEnvironments(singletonMap("env1", dto.createDto(EnvironmentDto.class))); + wsConfig.setEnvironments(singletonMap("env1", DTO.createDto(EnvironmentDto.class))); wsConfig.setDefaultEnv("env1"); ws.setStatus(WorkspaceStatus.RUNNING); wsConfig.setCommands(singletonList( - dto.createDto(CommandDto.class).withName("MCI").withType("mvn").withCommandLine("clean install"))); + DTO.createDto(CommandDto.class).withName("MCI").withType("mvn").withCommandLine("clean install"))); ws.setConfig(wsConfig.build()); WorkspaceImpl usersWorkspace = ws.build(); @@ -1260,203 +652,160 @@ public class FactoryServiceTest { // when Response response = given().auth() - .basic(JettyHttpServer.ADMIN_USER_NAME, JettyHttpServer.ADMIN_USER_PASSWORD) + .basic(ADMIN_USER_NAME, ADMIN_USER_PASSWORD) .when() - .get("/private" + SERVICE_PATH + "/workspace/" + wsId); + .get(SERVICE_PATH + "/workspace/" + wsId); // then assertEquals(response.getStatusCode(), BAD_REQUEST.getStatusCode()); } - - - /** - * Check that if no resolver is plugged, we have correct error - */ @Test - public void noResolver() throws Exception { - Set resolvers = new HashSet<>(); - when(factoryParametersResolvers.stream()).thenReturn(resolvers.stream()); + public void shouldResponse404OnGetSnippetIfFactoryDoesNotExist() throws Exception { + // given + doThrow(new NotFoundException("Factory URL with id " + FACTORY_ID + " is not found.")) + .when(factoryManager).getFactorySnippet(anyString(), anyString(), any()); - Map map = new HashMap<>(); - // when - Response response = given().contentType(ContentType.JSON).when().body(map).post(SERVICE_PATH_RESOLVER); + // when, then + final Response response = given().expect() + .statusCode(404) + .when() + .get(SERVICE_PATH + "/" + FACTORY_ID + "/snippet?type=url"); - // then check we have a not found - assertEquals(response.getStatusCode(), NOT_FOUND.getStatusCode()); - assertTrue(response.getBody().prettyPrint().contains(ERROR_NO_RESOLVER_AVAILABLE)); - } - - - /** - * Check that if there is a matching resolver, factory is created - */ - @Test - public void matchingResolver() throws Exception { - Set resolvers = new HashSet<>(); - when(factoryParametersResolvers.stream()).thenReturn(resolvers.stream()); - FactoryParametersResolver dummyResolver = mock(FactoryParametersResolver.class); - resolvers.add(dummyResolver); - - // create factory - Factory expectFactory = dto.createDto(Factory.class).withV("4.0").withName("matchingResolverFactory"); - - // accept resolver - when(dummyResolver.accept(anyMap())).thenReturn(TRUE); - when(dummyResolver.createFactory(anyMap())).thenReturn(expectFactory); - - // when - Map map = new HashMap<>(); - Response response = given().contentType(ContentType.JSON).when().body(map).post(SERVICE_PATH_RESOLVER); - - // then check we have a not found - assertEquals(response.getStatusCode(), OK.getStatusCode()); - Factory responseFactory = dto.createDtoFromJson(response.getBody().asInputStream(), Factory.class); - assertNotNull(responseFactory); - assertEquals(responseFactory.getName(), expectFactory.getName()); - assertEquals(responseFactory.getV(), expectFactory.getV()); - - // check we call resolvers - verify(dummyResolver).accept(anyMap()); - verify(dummyResolver).createFactory(anyMap()); - } - - - /** - * Check that if there is no matching resolver, there is error - */ - @Test - public void notMatchingResolver() throws Exception { - Set resolvers = new HashSet<>(); - when(factoryParametersResolvers.stream()).thenReturn(resolvers.stream()); - - FactoryParametersResolver dummyResolver = mock(FactoryParametersResolver.class); - resolvers.add(dummyResolver); - FactoryParametersResolver fooResolver = mock(FactoryParametersResolver.class); - resolvers.add(fooResolver); - - - // accept resolver - when(dummyResolver.accept(anyMap())).thenReturn(FALSE); - when(fooResolver.accept(anyMap())).thenReturn(FALSE); - - // when - Map map = new HashMap<>(); - Response response = given().contentType(ContentType.JSON).when().body(map).post(SERVICE_PATH_RESOLVER); - - // then check we have a not found - assertEquals(response.getStatusCode(), NOT_FOUND.getStatusCode()); - - // check we never call create factories on resolver - verify(dummyResolver, never()).createFactory(anyMap()); - verify(fooResolver, never()).createFactory(anyMap()); + assertEquals(getFromResponse(response, ServiceError.class).getMessage(), + "Factory URL with id " + FACTORY_ID + " is not found."); } /** - * Check that if there is a matching resolver and other not matching, factory is created + * Checks that the user can remove an existing factory */ @Test - public void onlyOneMatchingResolver() throws Exception { - Set resolvers = new HashSet<>(); - when(factoryParametersResolvers.stream()).thenReturn(resolvers.stream()); + public void shouldBeAbleToRemoveFactory() throws Exception { + final Factory factory = createFactory(); + when(factoryManager.getById(FACTORY_ID)).thenReturn(factory); - FactoryParametersResolver dummyResolver = mock(FactoryParametersResolver.class); - resolvers.add(dummyResolver); - FactoryParametersResolver fooResolver = mock(FactoryParametersResolver.class); - resolvers.add(fooResolver); + final Response response = given().auth() + .basic(ADMIN_USER_NAME, ADMIN_USER_PASSWORD) + .param("id", FACTORY_ID) + .when() + .delete(SERVICE_PATH + "/" + FACTORY_ID); - // create factory - Factory expectFactory = dto.createDto(Factory.class).withV("4.0").withName("matchingResolverFactory"); + assertEquals(response.getStatusCode(), 204); - // accept resolver - when(dummyResolver.accept(anyMap())).thenReturn(TRUE); - when(dummyResolver.createFactory(anyMap())).thenReturn(expectFactory); - when(fooResolver.accept(anyMap())).thenReturn(FALSE); - - // when - Map map = new HashMap<>(); - Response response = given().contentType(ContentType.JSON).when().body(map).post(SERVICE_PATH_RESOLVER); - - // then check we have a not found - assertEquals(response.getStatusCode(), OK.getStatusCode()); - Factory responseFactory = dto.createDtoFromJson(response.getBody().asInputStream(), Factory.class); - assertNotNull(responseFactory); - assertEquals(responseFactory.getName(), expectFactory.getName()); - assertEquals(responseFactory.getV(), expectFactory.getV()); - - // check we call resolvers - verify(dummyResolver).accept(anyMap()); - verify(dummyResolver).createFactory(anyMap()); - // never called this resolver - verify(fooResolver, never()).createFactory(anyMap()); + // check there was a call on the remove operation with expected ID + verify(factoryManager).removeFactory(FACTORY_ID); } + @Test + public void shouldNotThrowExceptionWhenRemoveNoExistingFactory() throws Exception { + doNothing().when(factoryManager).removeFactory(FACTORY_ID); + final Response response = given().auth() + .basic(ADMIN_USER_NAME, ADMIN_USER_PASSWORD) + .param("id", FACTORY_ID) + .when() + .delete(SERVICE_PATH + "/" + FACTORY_ID); + + assertEquals(response.getStatusCode(), 204); + } - /** - * Check that if there is a matching resolver, that factory is valid - */ @Test public void checkValidateResolver() throws Exception { - Set resolvers = new HashSet<>(); - when(factoryParametersResolvers.stream()).thenReturn(resolvers.stream()); - - FactoryParametersResolver dummyResolver = mock(FactoryParametersResolver.class); - resolvers.add(dummyResolver); + final FactoryParametersResolver dummyResolver = Mockito.mock(FactoryParametersResolver.class); + factoryParametersResolvers.add(dummyResolver); // invalid factory - String invalidFactoryMessage = "invalid factory"; + final String invalidFactoryMessage = "invalid factory"; doThrow(new BadRequestException(invalidFactoryMessage)).when(acceptValidator).validateOnAccept(any()); // create factory - Factory expectFactory = dto.createDto(Factory.class).withV("4.0").withName("matchingResolverFactory"); + final FactoryDto expectFactory = DTO.createDto(FactoryDto.class).withV("4.0").withName("matchingResolverFactory"); // accept resolver - when(dummyResolver.accept(anyMap())).thenReturn(TRUE); - when(dummyResolver.createFactory(anyMap())).thenReturn(expectFactory); + when(dummyResolver.accept(anyMapOf(String.class, String.class))).thenReturn(true); + when(dummyResolver.createFactory(anyMapOf(String.class, String.class))).thenReturn(expectFactory); // when - Map map = new HashMap<>(); - Response response = given().contentType(ContentType.JSON).when().body(map).queryParam(VALIDATE_QUERY_PARAMETER, valueOf(true)).post( - SERVICE_PATH_RESOLVER); + final Map map = new HashMap<>(); + final Response response = given().contentType(ContentType.JSON) + .when() + .body(map) + .queryParam(VALIDATE_QUERY_PARAMETER, valueOf(true)) + .post(SERVICE_PATH + "/resolver"); // then check we have a not found assertEquals(response.getStatusCode(), BAD_REQUEST.getStatusCode()); assertTrue(response.getBody().prettyPrint().contains(invalidFactoryMessage)); // check we call resolvers - verify(dummyResolver).accept(anyMap()); - verify(dummyResolver).createFactory(anyMap()); + verify(dummyResolver).accept(anyMapOf(String.class, String.class)); + verify(dummyResolver).createFactory(anyMapOf(String.class, String.class)); // check we call validator verify(acceptValidator).validateOnAccept(any()); - } - private Factory prepareFactoryWithGivenStorage(String type, String location) { - return dto.createDto(Factory.class) - .withV("4.0") - .withWorkspace(dto.createDto(WorkspaceConfigDto.class) - .withProjects(singletonList(dto.createDto(ProjectConfigDto.class) - .withSource(dto.createDto(SourceStorageDto.class) - .withType(type) - .withLocation(location))))); + private Factory createFactory() { + return createNamedFactory(FACTORY_NAME); } - private class FactorySaveAnswer implements Answer { - - private Factory savedFactory; - - @Override - public Object answer(InvocationOnMock invocation) throws Throwable { - if (savedFactory == null) { - savedFactory = (Factory)invocation.getArguments()[0]; - return CORRECT_FACTORY_ID; - } - Factory clone = dto.clone(savedFactory); - assertNotNull(clone); - return clone.withId(CORRECT_FACTORY_ID); - } + private Factory createNamedFactory(String name) { + return createFactoryWithStorage(name, PROJECT_SOURCE_TYPE, PROJECT_SOURCE_LOCATION); } + private Factory createFactoryWithStorage(String name, String type, String location) { + return FactoryImpl.builder() + .setId(FACTORY_ID) + .setVersion("4.0") + .setWorkspace(createWorkspaceConfig(type, location)) + .setCreator(new AuthorImpl(USER_ID, 12L)) + .setName(name) + .build(); + } + + private static WorkspaceConfig createWorkspaceConfig(String type, String location) { + return WorkspaceConfigImpl.builder() + .setName(WORKSPACE_NAME) + .setEnvironments(singletonMap("env1", new EnvironmentImpl(createEnvDto()))) + .setProjects(createProjects(type, location)) + .build(); + } + + private static EnvironmentDto createEnvDto() { + final EnvironmentRecipeImpl environmentRecipe = new EnvironmentRecipeImpl(); + environmentRecipe.setType("type"); + environmentRecipe.setContent("content"); + environmentRecipe.setContentType("compose"); + environmentRecipe.setLocation("location"); + final EnvironmentImpl env = new EnvironmentImpl(); + final ExtendedMachineImpl extendedMachine = new ExtendedMachineImpl(); + extendedMachine.setAgents(singletonList("agent")); + extendedMachine.setAttributes(singletonMap("att1", "value")); + extendedMachine.setServers(singletonMap("agent", new ServerConf2Impl("5555", + "https", + singletonMap("prop1", "value1")))); + env.setRecipe(environmentRecipe); + env.setMachines(singletonMap("machine1", extendedMachine)); + return org.eclipse.che.api.workspace.server.DtoConverter.asDto(env); + } + + private static List createProjects(String type, String location) { + final ProjectConfigImpl projectConfig = new ProjectConfigImpl(); + projectConfig.setSource(new SourceStorageImpl(type, location, null)); + return ImmutableList.of(projectConfig); + } + + private static T getFromResponse(Response response, Class clazz) throws Exception { + return DTO.createDtoFromJson(response.getBody().asInputStream(), clazz); + } + + private static List unwrapDtoList(Response response, Class dtoClass) { + return FluentIterable.from(DtoFactory.getInstance().createListDtoFromJson(response.body().print(), dtoClass)).toList(); + } + + private static Path getImagePath() throws Exception { + final URL res = currentThread().getContextClassLoader().getResource("100x100_image.jpeg"); + assertNotNull(res); + return Paths.get(res.toURI()); + } } diff --git a/wsmaster/che-core-api-factory/src/test/java/org/eclipse/che/api/factory/server/builder/FactoryBuilderTest.java b/wsmaster/che-core-api-factory/src/test/java/org/eclipse/che/api/factory/server/builder/FactoryBuilderTest.java index 7ca4eb4544..4405f1af14 100644 --- a/wsmaster/che-core-api-factory/src/test/java/org/eclipse/che/api/factory/server/builder/FactoryBuilderTest.java +++ b/wsmaster/che-core-api-factory/src/test/java/org/eclipse/che/api/factory/server/builder/FactoryBuilderTest.java @@ -14,17 +14,18 @@ import com.google.common.collect.ImmutableMap; import org.eclipse.che.api.core.ApiException; import org.eclipse.che.api.core.factory.FactoryParameter; +import org.eclipse.che.api.core.model.factory.Button; import org.eclipse.che.api.factory.server.impl.SourceStorageParametersValidator; -import org.eclipse.che.api.factory.shared.dto.Action; -import org.eclipse.che.api.factory.shared.dto.Author; -import org.eclipse.che.api.factory.shared.dto.Button; -import org.eclipse.che.api.factory.shared.dto.ButtonAttributes; -import org.eclipse.che.api.factory.shared.dto.Factory; -import org.eclipse.che.api.factory.shared.dto.Ide; -import org.eclipse.che.api.factory.shared.dto.OnAppClosed; -import org.eclipse.che.api.factory.shared.dto.OnAppLoaded; -import org.eclipse.che.api.factory.shared.dto.OnProjectsLoaded; -import org.eclipse.che.api.factory.shared.dto.Policies; +import org.eclipse.che.api.factory.shared.dto.AuthorDto; +import org.eclipse.che.api.factory.shared.dto.ButtonAttributesDto; +import org.eclipse.che.api.factory.shared.dto.ButtonDto; +import org.eclipse.che.api.factory.shared.dto.FactoryDto; +import org.eclipse.che.api.factory.shared.dto.IdeActionDto; +import org.eclipse.che.api.factory.shared.dto.IdeDto; +import org.eclipse.che.api.factory.shared.dto.OnAppClosedDto; +import org.eclipse.che.api.factory.shared.dto.OnAppLoadedDto; +import org.eclipse.che.api.factory.shared.dto.OnProjectsLoadedDto; +import org.eclipse.che.api.factory.shared.dto.PoliciesDto; import org.eclipse.che.api.machine.shared.dto.CommandDto; import org.eclipse.che.api.machine.shared.dto.MachineConfigDto; import org.eclipse.che.api.machine.shared.dto.MachineSourceDto; @@ -50,13 +51,14 @@ import java.net.URISyntaxException; import static java.util.Arrays.asList; import static java.util.Collections.singletonList; import static java.util.Collections.singletonMap; +import static java.util.Objects.requireNonNull; import static org.eclipse.che.dto.server.DtoFactory.newDto; import static org.mockito.Matchers.any; import static org.mockito.Matchers.eq; import static org.mockito.Mockito.verify; /** - * Tests for {@link org.eclipse.che.api.factory.shared.dto.Factory} + * Tests for {@link FactoryDto} * * @author Alexander Garagatyi * @author Sergii Kabashniuk @@ -69,9 +71,9 @@ public class FactoryBuilderTest { private FactoryBuilder factoryBuilder; - private Factory actual; + private FactoryDto actual; - private Factory expected; + private FactoryDto expected; @Mock private SourceStorageParametersValidator sourceProjectParametersValidator; @@ -81,7 +83,7 @@ public class FactoryBuilderTest { factoryBuilder = new FactoryBuilder(sourceProjectParametersValidator); actual = prepareFactory(); - expected = dto.createDto(Factory.class); + expected = dto.createDto(FactoryDto.class); } @Test @@ -92,50 +94,49 @@ public class FactoryBuilderTest { } @Test(expectedExceptions = ApiException.class) - public void shouldNotValidateUnparseableFactory() throws ApiException, URISyntaxException { + public void shouldNotValidateUnparseableFactory() throws Exception { factoryBuilder.checkValid(null); } @Test(expectedExceptions = ApiException.class, dataProvider = "setByServerParamsProvider", expectedExceptionsMessageRegExp = "You have provided an invalid parameter .* for this version of Factory parameters.*") - public void shouldNotAllowUsingParamsThatCanBeSetOnlyByServer(Factory factory) - throws InvocationTargetException, IllegalAccessException, ApiException, NoSuchMethodException { + public void shouldNotAllowUsingParamsThatCanBeSetOnlyByServer(FactoryDto factory) throws Exception { factoryBuilder.checkValid(factory); } @Test(dataProvider = "setByServerParamsProvider") - public void shouldAllowUsingParamsThatCanBeSetOnlyByServerDuringUpdate(Factory factory) - throws InvocationTargetException, IllegalAccessException, ApiException, NoSuchMethodException { + public void shouldAllowUsingParamsThatCanBeSetOnlyByServerDuringUpdate(FactoryDto factory) throws Exception { factoryBuilder.checkValid(factory, true); } @DataProvider(name = "setByServerParamsProvider") - public static Object[][] setByServerParamsProvider() throws URISyntaxException, IOException, NoSuchMethodException { - Factory factory = prepareFactory(); + public static Object[][] setByServerParamsProvider() throws Exception { + FactoryDto factory = prepareFactory(); return new Object[][] { - {dto.clone(factory).withId("id")}, - {dto.clone(factory).withCreator(dto.createDto(Author.class) + {requireNonNull(dto.clone(factory)).withId("id")}, + {requireNonNull(dto.clone(factory)).withCreator(dto.createDto(AuthorDto.class) .withUserId("id"))}, - {dto.clone(factory).withCreator(dto.createDto(Author.class) + {requireNonNull(dto.clone(factory)).withCreator(dto.createDto(AuthorDto.class) .withCreated(123L))} }; } @Test(expectedExceptions = ApiException.class, dataProvider = "notValidParamsProvider") - public void shouldNotAllowUsingNotValidParams(Factory factory) + public void shouldNotAllowUsingNotValidParams(FactoryDto factory) throws InvocationTargetException, IllegalAccessException, ApiException, NoSuchMethodException { factoryBuilder.checkValid(factory); } @DataProvider(name = "notValidParamsProvider") public static Object[][] notValidParamsProvider() throws URISyntaxException, IOException, NoSuchMethodException { - Factory factory = prepareFactory(); + FactoryDto factory = prepareFactory(); EnvironmentDto environmentDto = factory.getWorkspace().getEnvironments().values().iterator().next(); environmentDto.getRecipe().withType(null); - return new Object[][] { - {dto.clone(factory).withWorkspace(factory.getWorkspace().withDefaultEnv(null)) }, - {dto.clone(factory).withWorkspace(factory.getWorkspace().withEnvironments(singletonMap("test", environmentDto))) } + {requireNonNull(dto.clone(factory)).withWorkspace(factory.getWorkspace() + .withDefaultEnv(null)) }, + {requireNonNull(dto.clone(factory)).withWorkspace(factory.getWorkspace() + .withEnvironments(singletonMap("test", environmentDto)))} }; } @@ -143,8 +144,8 @@ public class FactoryBuilderTest { public void shouldBeAbleToValidateV4_0WithTrackedParamsWithoutAccountIdIfOnPremisesIsEnabled() throws Exception { factoryBuilder = new FactoryBuilder(sourceProjectParametersValidator); - Factory factory = prepareFactory() - .withPolicies(dto.createDto(Policies.class) + FactoryDto factory = prepareFactory() + .withPolicies(dto.createDto(PoliciesDto.class) .withReferer("referrer") .withSince(123L) .withUntil(123L)); @@ -152,7 +153,7 @@ public class FactoryBuilderTest { factoryBuilder.checkValid(factory); } - private static Factory prepareFactory() { + private static FactoryDto prepareFactory() { ProjectConfigDto project = dto.createDto(ProjectConfigDto.class) .withSource(dto.createDto(SourceStorageDto.class) .withType("git") @@ -162,20 +163,6 @@ public class FactoryBuilderTest { .withDescription("description") .withName("name") .withPath("/path"); - MachineConfigDto machineConfig = dto.createDto(MachineConfigDto.class) - .withName("name") - .withType("docker") - .withDev(true) - .withSource(dto.createDto(MachineSourceDto.class) - .withType("git") - .withLocation("https://github.com/123/test.git")) - .withServers(asList(newDto(ServerConfDto.class).withRef("ref1") - .withPort("8080") - .withProtocol("https"), - newDto(ServerConfDto.class).withRef("ref2") - .withPort("9090/udp") - .withProtocol("someprotocol"))) - .withEnvVariables(singletonMap("key1", "value1")); EnvironmentDto environment = dto.createDto(EnvironmentDto.class) .withRecipe(newDto(EnvironmentRecipeDto.class).withType("compose") .withContentType("application/x-yaml") @@ -192,26 +179,26 @@ public class FactoryBuilderTest { .withCommandLine("mvn test"))) .withDefaultEnv("env1") .withEnvironments(singletonMap("test", environment)); - Ide ide = dto.createDto(Ide.class) - .withOnAppClosed(dto.createDto(OnAppClosed.class) - .withActions(singletonList(dto.createDto(Action.class).withId("warnOnClose")))) - .withOnAppLoaded(dto.createDto(OnAppLoaded.class) - .withActions(asList(dto.createDto(Action.class) - .withId("newProject"), - dto.createDto(Action.class) - .withId("openWelcomePage") + IdeDto ide = dto.createDto(IdeDto.class) + .withOnAppClosed(dto.createDto(OnAppClosedDto.class) + .withActions(singletonList(dto.createDto(IdeActionDto.class).withId("warnOnClose")))) + .withOnAppLoaded(dto.createDto(OnAppLoadedDto.class) + .withActions(asList(dto.createDto(IdeActionDto.class) + .withId("newProject"), + dto.createDto(IdeActionDto.class) + .withId("openWelcomePage") .withProperties(ImmutableMap.of( "authenticatedTitle", "Greeting title for authenticated users", "authenticatedContentUrl", "http://example.com/content.url"))))) - .withOnProjectsLoaded(dto.createDto(OnProjectsLoaded.class) - .withActions(asList(dto.createDto(Action.class) + .withOnProjectsLoaded(dto.createDto(OnProjectsLoadedDto.class) + .withActions(asList(dto.createDto(IdeActionDto.class) .withId("openFile") .withProperties(singletonMap("file", "pom.xml")), - dto.createDto(Action.class) + dto.createDto(IdeActionDto.class) .withId("run"), - dto.createDto(Action.class) + dto.createDto(IdeActionDto.class) .withId("findReplace") .withProperties( ImmutableMap.of( @@ -223,19 +210,19 @@ public class FactoryBuilderTest { "NEW_VALUE_2", "replaceMode", "mode"))))); - return dto.createDto(Factory.class) + return dto.createDto(FactoryDto.class) .withV("4.0") .withWorkspace(workspaceConfig) - .withCreator(dto.createDto(Author.class) + .withCreator(dto.createDto(AuthorDto.class) .withEmail("email") .withName("name")) - .withPolicies(dto.createDto(Policies.class) + .withPolicies(dto.createDto(PoliciesDto.class) .withReferer("referrer") .withSince(123L) .withUntil(123L)) - .withButton(dto.createDto(Button.class) - .withType(Button.ButtonType.logo) - .withAttributes(dto.createDto(ButtonAttributes.class) + .withButton(dto.createDto(ButtonDto.class) + .withType(Button.Type.LOGO) + .withAttributes(dto.createDto(ButtonAttributesDto.class) .withColor("color") .withCounter(true) .withLogo("logo") diff --git a/wsmaster/che-core-api-factory/src/test/java/org/eclipse/che/api/factory/server/impl/FactoryBaseValidatorTest.java b/wsmaster/che-core-api-factory/src/test/java/org/eclipse/che/api/factory/server/impl/FactoryBaseValidatorTest.java index 37dd3c3369..967e5a6cfe 100644 --- a/wsmaster/che-core-api-factory/src/test/java/org/eclipse/che/api/factory/server/impl/FactoryBaseValidatorTest.java +++ b/wsmaster/che-core-api-factory/src/test/java/org/eclipse/che/api/factory/server/impl/FactoryBaseValidatorTest.java @@ -18,14 +18,14 @@ import org.eclipse.che.api.core.NotFoundException; import org.eclipse.che.api.core.ServerException; import org.eclipse.che.api.factory.server.FactoryConstants; import org.eclipse.che.api.factory.server.builder.FactoryBuilder; -import org.eclipse.che.api.factory.shared.dto.Action; -import org.eclipse.che.api.factory.shared.dto.Author; -import org.eclipse.che.api.factory.shared.dto.Factory; -import org.eclipse.che.api.factory.shared.dto.Ide; -import org.eclipse.che.api.factory.shared.dto.OnAppClosed; -import org.eclipse.che.api.factory.shared.dto.OnAppLoaded; -import org.eclipse.che.api.factory.shared.dto.OnProjectsLoaded; -import org.eclipse.che.api.factory.shared.dto.Policies; +import org.eclipse.che.api.factory.shared.dto.IdeActionDto; +import org.eclipse.che.api.factory.shared.dto.AuthorDto; +import org.eclipse.che.api.factory.shared.dto.FactoryDto; +import org.eclipse.che.api.factory.shared.dto.IdeDto; +import org.eclipse.che.api.factory.shared.dto.OnAppClosedDto; +import org.eclipse.che.api.factory.shared.dto.OnAppLoadedDto; +import org.eclipse.che.api.factory.shared.dto.OnProjectsLoadedDto; +import org.eclipse.che.api.factory.shared.dto.PoliciesDto; 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.UserDao; @@ -51,8 +51,11 @@ import java.util.Date; import java.util.HashMap; import java.util.List; import java.util.Map; +import java.util.Objects; import static java.util.Collections.singletonList; +import static java.util.Objects.*; +import static org.eclipse.che.dto.server.DtoFactory.*; import static org.eclipse.che.dto.server.DtoFactory.newDto; import static org.mockito.Mockito.when; @@ -62,7 +65,6 @@ public class FactoryBaseValidatorTest { private static final String VALID_PROJECT_PATH = "/cloudide"; private static final String ID = "id"; - @Mock private UserDao userDao; @@ -77,19 +79,16 @@ public class FactoryBaseValidatorTest { private TesterFactoryBaseValidator validator; - private Factory factory; + private FactoryDto factory; @BeforeMethod public void setUp() throws ParseException, NotFoundException, ServerException { - factory = newDto(Factory.class) - .withV("4.0") - .withCreator(newDto(Author.class) - .withUserId("userid")); - - UserImpl user = new UserImpl("userid"); + factory = newDto(FactoryDto.class).withV("4.0") + .withCreator(newDto(AuthorDto.class).withUserId("userid")); + final UserImpl user = new UserImpl("userid", "email", "name"); when(userDao.getById("userid")).thenReturn(user); - validator = new TesterFactoryBaseValidator(preferenceDao); + validator = new TesterFactoryBaseValidator(); } @Test @@ -97,7 +96,6 @@ public class FactoryBaseValidatorTest { factory = prepareFactoryWithGivenStorage("git", VALID_REPOSITORY_URL, VALID_PROJECT_PATH); validator.validateProjects(factory); validator.validateProjects(factory); - validator.validateAccountId(factory); } @Test @@ -105,7 +103,6 @@ public class FactoryBaseValidatorTest { factory = prepareFactoryWithGivenStorage("esbwso2", VALID_REPOSITORY_URL, VALID_PROJECT_PATH); validator.validateProjects(factory); validator.validateProjects(factory); - validator.validateAccountId(factory); } @Test(expectedExceptions = ApiException.class, @@ -151,15 +148,15 @@ public class FactoryBaseValidatorTest { } @Test(dataProvider = "badAdvancedFactoryUrlProvider", expectedExceptions = ApiException.class) - public void shouldNotValidateIfStorageOrStorageLocationIsInvalid(Factory factory) throws ApiException { + public void shouldNotValidateIfStorageOrStorageLocationIsInvalid(FactoryDto factory) throws ApiException { validator.validateProjects(factory); } @DataProvider(name = "badAdvancedFactoryUrlProvider") public Object[][] invalidParametersFactoryUrlProvider() throws UnsupportedEncodingException { - Factory adv1 = prepareFactoryWithGivenStorage("notagit", VALID_REPOSITORY_URL, VALID_PROJECT_PATH); - Factory adv2 = prepareFactoryWithGivenStorage("git", null, VALID_PROJECT_PATH); - Factory adv3 = prepareFactoryWithGivenStorage("git", "", VALID_PROJECT_PATH); + FactoryDto adv1 = prepareFactoryWithGivenStorage("notagit", VALID_REPOSITORY_URL, VALID_PROJECT_PATH); + FactoryDto adv2 = prepareFactoryWithGivenStorage("git", null, VALID_PROJECT_PATH); + FactoryDto adv3 = prepareFactoryWithGivenStorage("git", "", VALID_PROJECT_PATH); return new Object[][]{ {adv1},// invalid vcs {adv2},// invalid vcsurl @@ -172,9 +169,9 @@ public class FactoryBaseValidatorTest { "digits or these following special characters -._.") public void shouldThrowFactoryUrlExceptionIfProjectNameInvalid(String projectName) throws Exception { // given - factory.withWorkspace(newDto(WorkspaceConfigDto.class).withProjects(Collections.singletonList(newDto(ProjectConfigDto.class) - .withType("type") - .withName(projectName)))); + factory.withWorkspace(newDto(WorkspaceConfigDto.class).withProjects(singletonList(newDto(ProjectConfigDto.class) + .withType("type") + .withName(projectName)))); // when, then validator.validateProjects(factory); } @@ -184,12 +181,12 @@ public class FactoryBaseValidatorTest { // given prepareFactoryWithGivenStorage("git", VALID_REPOSITORY_URL, VALID_PROJECT_PATH); factory.withWorkspace(newDto(WorkspaceConfigDto.class).withProjects( - Collections.singletonList(newDto(ProjectConfigDto.class).withType("type") - .withName(projectName) - .withSource(newDto(SourceStorageDto.class) - .withType("git") - .withLocation(VALID_REPOSITORY_URL)) - .withPath(VALID_PROJECT_PATH)))); + singletonList(newDto(ProjectConfigDto.class).withType("type") + .withName(projectName) + .withSource(newDto(SourceStorageDto.class) + .withType("git") + .withLocation(VALID_REPOSITORY_URL)) + .withPath(VALID_PROJECT_PATH)))); // when, then validator.validateProjects(factory); } @@ -222,23 +219,12 @@ public class FactoryBaseValidatorTest { }; } - @Test - public void shouldBeAbleToValidateIfOrgIdIsValid() throws ApiException, ParseException { - validator.validateAccountId(factory); - } - - @Test - public void shouldBeAbleToValidateIfOrgIdAndOwnerAreValid() - throws ApiException, ParseException { - // when, then - validator.validateAccountId(factory); - } @Test public void shouldValidateIfCurrentTimeBeforeSinceUntil() throws Exception { Long currentTime = new Date().getTime(); - factory.withPolicies(newDto(Policies.class) + factory.withPolicies(newDto(PoliciesDto.class) .withSince(currentTime + 10000L) .withUntil(currentTime + 20000L)); validator.validateCurrentTimeAfterSinceUntil(factory); @@ -247,7 +233,7 @@ public class FactoryBaseValidatorTest { @Test(expectedExceptions = ApiException.class, expectedExceptionsMessageRegExp = FactoryConstants.INVALID_SINCE_MESSAGE) public void shouldNotValidateIfSinceBeforeCurrent() throws ApiException { - factory.withPolicies(newDto(Policies.class) + factory.withPolicies(newDto(PoliciesDto.class) .withSince(1L)); validator.validateCurrentTimeAfterSinceUntil(factory); } @@ -255,7 +241,7 @@ public class FactoryBaseValidatorTest { @Test(expectedExceptions = ApiException.class, expectedExceptionsMessageRegExp = FactoryConstants.INVALID_UNTIL_MESSAGE) public void shouldNotValidateIfUntilBeforeCurrent() throws ApiException { - factory.withPolicies(newDto(Policies.class) + factory.withPolicies(newDto(PoliciesDto.class) .withUntil(1L)); validator.validateCurrentTimeAfterSinceUntil(factory); } @@ -263,7 +249,7 @@ public class FactoryBaseValidatorTest { @Test(expectedExceptions = ApiException.class, expectedExceptionsMessageRegExp = FactoryConstants.INVALID_SINCEUNTIL_MESSAGE) public void shouldNotValidateIfUntilBeforeSince() throws ApiException { - factory.withPolicies(newDto(Policies.class) + factory.withPolicies(newDto(PoliciesDto.class) .withSince(2L) .withUntil(1L)); @@ -274,7 +260,7 @@ public class FactoryBaseValidatorTest { expectedExceptionsMessageRegExp = FactoryConstants.ILLEGAL_FACTORY_BY_UNTIL_MESSAGE) public void shouldNotValidateIfUntilBeforeCurrentTime() throws ApiException { Long currentTime = new Date().getTime(); - factory.withPolicies(newDto(Policies.class) + factory.withPolicies(newDto(PoliciesDto.class) .withUntil(currentTime - 10000L)); validator.validateCurrentTimeBetweenSinceUntil(factory); @@ -284,7 +270,7 @@ public class FactoryBaseValidatorTest { public void shouldValidateIfCurrentTimeBetweenUntilSince() throws ApiException { Long currentTime = new Date().getTime(); - factory.withPolicies(newDto(Policies.class) + factory.withPolicies(newDto(PoliciesDto.class) .withSince(currentTime - 10000L) .withUntil(currentTime + 10000L)); @@ -295,7 +281,7 @@ public class FactoryBaseValidatorTest { expectedExceptionsMessageRegExp = FactoryConstants.ILLEGAL_FACTORY_BY_SINCE_MESSAGE) public void shouldNotValidateIfUntilSinceAfterCurrentTime() throws ApiException { Long currentTime = new Date().getTime(); - factory.withPolicies(newDto(Policies.class) + factory.withPolicies(newDto(PoliciesDto.class) .withSince(currentTime + 10000L)); validator.validateCurrentTimeBetweenSinceUntil(factory); @@ -304,29 +290,27 @@ public class FactoryBaseValidatorTest { @Test public void shouldValidateTrackedParamsIfOrgIdIsMissingButOnPremisesTrue() throws Exception { - final DtoFactory dtoFactory = DtoFactory.getInstance(); - Factory factory = dtoFactory.createDto(Factory.class); + final DtoFactory dtoFactory = getInstance(); + FactoryDto factory = dtoFactory.createDto(FactoryDto.class); factory.withV("4.0") - .withPolicies(dtoFactory.createDto(Policies.class) + .withPolicies(dtoFactory.createDto(PoliciesDto.class) .withSince(System.currentTimeMillis() + 1_000_000) .withUntil(System.currentTimeMillis() + 10_000_000) .withReferer("codenvy.com")); - validator = new TesterFactoryBaseValidator(preferenceDao); - - validator.validateAccountId(factory); + validator = new TesterFactoryBaseValidator(); } @Test(expectedExceptions = BadRequestException.class) public void shouldNotValidateOpenfileActionIfInWrongSectionOnAppClosed() throws Exception { //given - validator = new TesterFactoryBaseValidator(preferenceDao); - List actions = Arrays.asList(newDto(Action.class) - .withId("openFile")); - Ide ide = newDto(Ide.class) - .withOnAppClosed(newDto(OnAppClosed.class) + validator = new TesterFactoryBaseValidator(); + List actions = singletonList(newDto(IdeActionDto.class) + .withId("openFile")); + IdeDto ide = newDto(IdeDto.class) + .withOnAppClosed(newDto(OnAppClosedDto.class) .withActions(actions)); - Factory factoryWithAccountId = DtoFactory.getInstance().clone(factory).withIde(ide); + FactoryDto factoryWithAccountId = requireNonNull(getInstance().clone(factory)).withIde(ide); //when validator.validateProjectActions(factoryWithAccountId); } @@ -334,13 +318,13 @@ public class FactoryBaseValidatorTest { @Test(expectedExceptions = BadRequestException.class) public void shouldNotValidateFindReplaceActionIfInWrongSectionOnAppLoaded() throws Exception { //given - validator = new TesterFactoryBaseValidator(preferenceDao); - List actions = Arrays.asList(newDto(Action.class) - .withId("findReplace")); - Ide ide = newDto(Ide.class) - .withOnAppLoaded(newDto(OnAppLoaded.class) + validator = new TesterFactoryBaseValidator(); + List actions = singletonList(newDto(IdeActionDto.class) + .withId("findReplace")); + IdeDto ide = newDto(IdeDto.class) + .withOnAppLoaded(newDto(OnAppLoadedDto.class) .withActions(actions)); - Factory factoryWithAccountId = DtoFactory.getInstance().clone(factory).withIde(ide); + FactoryDto factoryWithAccountId = requireNonNull(getInstance().clone(factory)).withIde(ide); //when validator.validateProjectActions(factoryWithAccountId); } @@ -348,13 +332,13 @@ public class FactoryBaseValidatorTest { @Test(expectedExceptions = BadRequestException.class) public void shouldNotValidateIfOpenfileActionInsufficientParams() throws Exception { //given - validator = new TesterFactoryBaseValidator(preferenceDao); - List actions = Arrays.asList(newDto(Action.class) - .withId("openFile")); - Ide ide = newDto(Ide.class) - .withOnProjectsLoaded(newDto(OnProjectsLoaded.class) + validator = new TesterFactoryBaseValidator(); + List actions = singletonList(newDto(IdeActionDto.class) + .withId("openFile")); + IdeDto ide = newDto(IdeDto.class) + .withOnProjectsLoaded(newDto(OnProjectsLoadedDto.class) .withActions(actions)); - Factory factoryWithAccountId = DtoFactory.getInstance().clone(factory).withIde(ide); + FactoryDto factoryWithAccountId = requireNonNull(getInstance().clone(factory)).withIde(ide); //when validator.validateProjectActions(factoryWithAccountId); } @@ -362,13 +346,13 @@ public class FactoryBaseValidatorTest { @Test(expectedExceptions = BadRequestException.class) public void shouldNotValidateIfrunCommandActionInsufficientParams() throws Exception { //given - validator = new TesterFactoryBaseValidator(preferenceDao); - List actions = Arrays.asList(newDto(Action.class) - .withId("openFile")); - Ide ide = newDto(Ide.class) - .withOnProjectsLoaded(newDto(OnProjectsLoaded.class) + validator = new TesterFactoryBaseValidator(); + List actions = singletonList(newDto(IdeActionDto.class) + .withId("openFile")); + IdeDto ide = newDto(IdeDto.class) + .withOnProjectsLoaded(newDto(OnProjectsLoadedDto.class) .withActions(actions)); - Factory factoryWithAccountId = DtoFactory.getInstance().clone(factory).withIde(ide); + FactoryDto factoryWithAccountId = requireNonNull(getInstance().clone(factory)).withIde(ide); //when validator.validateProjectActions(factoryWithAccountId); } @@ -376,13 +360,13 @@ public class FactoryBaseValidatorTest { @Test(expectedExceptions = BadRequestException.class) public void shouldNotValidateIfOpenWelcomePageActionInsufficientParams() throws Exception { //given - validator = new TesterFactoryBaseValidator(preferenceDao); - List actions = Arrays.asList(newDto(Action.class) - .withId("openWelcomePage")); - Ide ide = newDto(Ide.class) - .withOnAppLoaded((newDto(OnAppLoaded.class) - .withActions(actions))); - Factory factoryWithAccountId = DtoFactory.getInstance().clone(factory).withIde(ide); + validator = new TesterFactoryBaseValidator(); + List actions = singletonList(newDto(IdeActionDto.class) + .withId("openWelcomePage")); + IdeDto ide = newDto(IdeDto.class) + .withOnAppLoaded((newDto(OnAppLoadedDto.class) + .withActions(actions))); + FactoryDto factoryWithAccountId = requireNonNull(getInstance().clone(factory)).withIde(ide); //when validator.validateProjectActions(factoryWithAccountId); } @@ -390,18 +374,18 @@ public class FactoryBaseValidatorTest { @Test(expectedExceptions = BadRequestException.class) public void shouldNotValidateIfFindReplaceActionInsufficientParams() throws Exception { //given - validator = new TesterFactoryBaseValidator(preferenceDao); + validator = new TesterFactoryBaseValidator(); Map params = new HashMap<>(); params.put("in", "pom.xml"); // find is missing! params.put("replace", "123"); - List actions = Arrays.asList(newDto(Action.class) - .withId("findReplace") - .withProperties(params)); - Ide ide = newDto(Ide.class) - .withOnProjectsLoaded(newDto(OnProjectsLoaded.class) + List actions = singletonList(newDto(IdeActionDto.class) + .withId("findReplace") + .withProperties(params)); + IdeDto ide = newDto(IdeDto.class) + .withOnProjectsLoaded(newDto(OnProjectsLoadedDto.class) .withActions(actions)); - Factory factoryWithAccountId = DtoFactory.getInstance().clone(factory).withIde(ide); + FactoryDto factoryWithAccountId = requireNonNull(getInstance().clone(factory)).withIde(ide); //when validator.validateProjectActions(factoryWithAccountId); } @@ -409,18 +393,18 @@ public class FactoryBaseValidatorTest { @Test public void shouldValidateFindReplaceAction() throws Exception { //given - validator = new TesterFactoryBaseValidator(preferenceDao); + validator = new TesterFactoryBaseValidator(); Map params = new HashMap<>(); params.put("in", "pom.xml"); params.put("find", "123"); params.put("replace", "456"); - List actions = Arrays.asList(newDto(Action.class) - .withId("findReplace") - .withProperties(params)); - Ide ide = newDto(Ide.class) - .withOnProjectsLoaded(newDto(OnProjectsLoaded.class) + List actions = singletonList(newDto(IdeActionDto.class) + .withId("findReplace") + .withProperties(params)); + IdeDto ide = newDto(IdeDto.class) + .withOnProjectsLoaded(newDto(OnProjectsLoadedDto.class) .withActions(actions)); - Factory factoryWithAccountId = DtoFactory.getInstance().clone(factory).withIde(ide); + FactoryDto factoryWithAccountId = requireNonNull(getInstance().clone(factory)).withIde(ide); //when validator.validateProjectActions(factoryWithAccountId); } @@ -428,16 +412,16 @@ public class FactoryBaseValidatorTest { @Test public void shouldValidateOpenfileAction() throws Exception { //given - validator = new TesterFactoryBaseValidator(preferenceDao); + validator = new TesterFactoryBaseValidator(); Map params = new HashMap<>(); params.put("file", "pom.xml"); - List actions = Arrays.asList(newDto(Action.class) - .withId("openFile") - .withProperties(params)); - Ide ide = newDto(Ide.class) - .withOnProjectsLoaded(newDto(OnProjectsLoaded.class) + List actions = singletonList(newDto(IdeActionDto.class) + .withId("openFile") + .withProperties(params)); + IdeDto ide = newDto(IdeDto.class) + .withOnProjectsLoaded(newDto(OnProjectsLoadedDto.class) .withActions(actions)); - Factory factoryWithAccountId = DtoFactory.getInstance().clone(factory).withIde(ide); + FactoryDto factoryWithAccountId = requireNonNull(getInstance().clone(factory)).withIde(ide); //when validator.validateProjectActions(factoryWithAccountId); } @@ -447,11 +431,11 @@ public class FactoryBaseValidatorTest { public Object[][] trackedFactoryParameterWithoutValidAccountId() throws URISyntaxException, IOException, NoSuchMethodException { return new Object[][]{ { - newDto(Factory.class) + newDto(FactoryDto.class) .withV("4.0") - .withIde(newDto(Ide.class) - .withOnAppLoaded(newDto(OnAppLoaded.class) - .withActions(singletonList(newDto(Action.class) + .withIde(newDto(IdeDto.class) + .withOnAppLoaded(newDto(OnAppLoadedDto.class) + .withActions(singletonList(newDto(IdeActionDto.class) .withId("openWelcomePage") .withProperties( ImmutableMap @@ -470,30 +454,30 @@ public class FactoryBaseValidatorTest { .put("nonAuthenticatedContentUrl", "url") .build())) - )))}, + )))}, - {newDto(Factory.class) + {newDto(FactoryDto.class) .withV("4.0") - .withPolicies(newDto(Policies.class) + .withPolicies(newDto(PoliciesDto.class) .withSince(10000L))}, - {newDto(Factory.class) + {newDto(FactoryDto.class) .withV("4.0") - .withPolicies(newDto(Policies.class) + .withPolicies(newDto(PoliciesDto.class) .withUntil(10000L))}, - {newDto(Factory.class) + {newDto(FactoryDto.class) .withV("4.0") - .withPolicies(newDto(Policies.class) + .withPolicies(newDto(PoliciesDto.class) .withReferer("host"))} }; } - private Factory prepareFactoryWithGivenStorage(String type, String location, String path) { + private FactoryDto prepareFactoryWithGivenStorage(String type, String location, String path) { return factory.withWorkspace(newDto(WorkspaceConfigDto.class) - .withProjects(Collections.singletonList(newDto(ProjectConfigDto.class) - .withSource(newDto(SourceStorageDto.class) - .withType(type) - .withLocation( - location)) - .withPath(path)))); + .withProjects(singletonList(newDto(ProjectConfigDto.class) + .withSource(newDto(SourceStorageDto.class) + .withType(type) + .withLocation( + location)) + .withPath(path)))); } } diff --git a/wsmaster/che-core-api-factory/src/test/java/org/eclipse/che/api/factory/server/impl/FactoryCreateAndAcceptValidatorsImplsTest.java b/wsmaster/che-core-api-factory/src/test/java/org/eclipse/che/api/factory/server/impl/FactoryCreateAndAcceptValidatorsImplsTest.java index 0df79abda2..be1fdc0dc8 100644 --- a/wsmaster/che-core-api-factory/src/test/java/org/eclipse/che/api/factory/server/impl/FactoryCreateAndAcceptValidatorsImplsTest.java +++ b/wsmaster/che-core-api-factory/src/test/java/org/eclipse/che/api/factory/server/impl/FactoryCreateAndAcceptValidatorsImplsTest.java @@ -12,7 +12,7 @@ package org.eclipse.che.api.factory.server.impl; import org.eclipse.che.api.core.ApiException; import org.eclipse.che.api.core.model.workspace.WorkspaceConfig; -import org.eclipse.che.api.factory.shared.dto.Factory; +import org.eclipse.che.api.factory.shared.dto.FactoryDto; import org.eclipse.che.api.user.server.spi.PreferenceDao; import org.eclipse.che.api.user.server.spi.UserDao; import org.eclipse.che.api.workspace.server.WorkspaceValidator; @@ -41,7 +41,7 @@ public class FactoryCreateAndAcceptValidatorsImplsTest { private PreferenceDao preferenceDao; @Mock - private Factory factory; + private FactoryDto factory; @Mock private WorkspaceValidator workspaceConfigValidator; @@ -54,49 +54,45 @@ public class FactoryCreateAndAcceptValidatorsImplsTest { @BeforeMethod public void setUp() throws Exception { - acceptValidator = new FactoryAcceptValidatorImpl(preferenceDao); - createValidator = new FactoryCreateValidatorImpl(preferenceDao, workspaceConfigValidator); + acceptValidator = new FactoryAcceptValidatorImpl(); + createValidator = new FactoryCreateValidatorImpl(workspaceConfigValidator); } @Test public void testValidateOnCreate() throws ApiException { FactoryCreateValidatorImpl spy = spy(createValidator); doNothing().when(spy) - .validateProjects(any(Factory.class)); + .validateProjects(any(FactoryDto.class)); doNothing().when(spy) - .validateAccountId(any(Factory.class)); + .validateCurrentTimeAfterSinceUntil(any(FactoryDto.class)); doNothing().when(spy) - .validateCurrentTimeAfterSinceUntil(any(Factory.class)); - doNothing().when(spy) - .validateProjectActions(any(Factory.class)); + .validateProjectActions(any(FactoryDto.class)); doNothing().when(workspaceConfigValidator) .validateConfig(any(WorkspaceConfig.class)); //main invoke spy.validateOnCreate(factory); - verify(spy).validateProjects(any(Factory.class)); - verify(spy).validateAccountId(any(Factory.class)); - verify(spy).validateCurrentTimeAfterSinceUntil(any(Factory.class)); - verify(spy).validateOnCreate(any(Factory.class)); - verify(spy).validateProjectActions(any(Factory.class)); + verify(spy).validateProjects(any(FactoryDto.class)); + verify(spy).validateCurrentTimeAfterSinceUntil(any(FactoryDto.class)); + verify(spy).validateOnCreate(any(FactoryDto.class)); + verify(spy).validateProjectActions(any(FactoryDto.class)); verifyNoMoreInteractions(spy); } - @Test public void testOnAcceptEncoded() throws ApiException { FactoryAcceptValidatorImpl spy = spy(acceptValidator); - doNothing().when(spy).validateCurrentTimeBetweenSinceUntil(any(Factory.class)); - doNothing().when(spy).validateProjectActions(any(Factory.class)); + doNothing().when(spy).validateCurrentTimeBetweenSinceUntil(any(FactoryDto.class)); + doNothing().when(spy).validateProjectActions(any(FactoryDto.class)); //main invoke spy.validateOnAccept(factory); - verify(spy).validateCurrentTimeBetweenSinceUntil(any(Factory.class)); - verify(spy).validateOnAccept(any(Factory.class)); - verify(spy).validateProjectActions(any(Factory.class)); + verify(spy).validateCurrentTimeBetweenSinceUntil(any(FactoryDto.class)); + verify(spy).validateOnAccept(any(FactoryDto.class)); + verify(spy).validateProjectActions(any(FactoryDto.class)); verifyNoMoreInteractions(spy); } diff --git a/wsmaster/che-core-api-factory/src/test/java/org/eclipse/che/api/factory/server/impl/FactoryEditValidatorImplTest.java b/wsmaster/che-core-api-factory/src/test/java/org/eclipse/che/api/factory/server/impl/FactoryEditValidatorImplTest.java index bf4665f807..f42cd84187 100644 --- a/wsmaster/che-core-api-factory/src/test/java/org/eclipse/che/api/factory/server/impl/FactoryEditValidatorImplTest.java +++ b/wsmaster/che-core-api-factory/src/test/java/org/eclipse/che/api/factory/server/impl/FactoryEditValidatorImplTest.java @@ -14,8 +14,8 @@ import org.eclipse.che.api.core.ApiException; import org.eclipse.che.api.core.ForbiddenException; import org.eclipse.che.api.core.ServerException; import org.eclipse.che.api.factory.server.FactoryEditValidator; -import org.eclipse.che.api.factory.shared.dto.Author; -import org.eclipse.che.api.factory.shared.dto.Factory; +import org.eclipse.che.api.factory.shared.dto.AuthorDto; +import org.eclipse.che.api.factory.shared.dto.FactoryDto; import org.eclipse.che.commons.env.EnvironmentContext; import org.eclipse.che.commons.subject.Subject; import org.mockito.InjectMocks; @@ -36,7 +36,7 @@ import static org.mockito.Mockito.when; public class FactoryEditValidatorImplTest { @Mock - private Factory factory; + private FactoryDto factory; @InjectMocks private FactoryEditValidator factoryEditValidator = new FactoryEditValidatorImpl(); @@ -60,7 +60,7 @@ public class FactoryEditValidatorImplTest { String userId = "florent"; setCurrentUser(userId); - Author author = mock(Author.class); + AuthorDto author = mock(AuthorDto.class); doReturn(author).when(factory) .getCreator(); doReturn("john").when(author) @@ -77,7 +77,7 @@ public class FactoryEditValidatorImplTest { public void testUserIsTheAuthor() throws ApiException { String userId = "florent"; setCurrentUser(userId); - Author author = mock(Author.class); + AuthorDto author = mock(AuthorDto.class); doReturn(author).when(factory) .getCreator(); doReturn(userId).when(author) diff --git a/wsmaster/che-core-api-factory/src/test/java/org/eclipse/che/api/factory/server/impl/TesterFactoryBaseValidator.java b/wsmaster/che-core-api-factory/src/test/java/org/eclipse/che/api/factory/server/impl/TesterFactoryBaseValidator.java index 5c5080b6df..094d2b8c8b 100644 --- a/wsmaster/che-core-api-factory/src/test/java/org/eclipse/che/api/factory/server/impl/TesterFactoryBaseValidator.java +++ b/wsmaster/che-core-api-factory/src/test/java/org/eclipse/che/api/factory/server/impl/TesterFactoryBaseValidator.java @@ -10,14 +10,11 @@ *******************************************************************************/ package org.eclipse.che.api.factory.server.impl; -import org.eclipse.che.api.user.server.spi.PreferenceDao; - /** * @author Sergii Kabashniuk */ public class TesterFactoryBaseValidator extends FactoryBaseValidator { - public TesterFactoryBaseValidator(PreferenceDao preferenceDao) { - super(preferenceDao); + public TesterFactoryBaseValidator() { } } diff --git a/wsmaster/che-core-api-factory/src/test/java/org/eclipse/che/api/factory/server/jpa/FactoryJpaTckRepository.java b/wsmaster/che-core-api-factory/src/test/java/org/eclipse/che/api/factory/server/jpa/FactoryJpaTckRepository.java new file mode 100644 index 0000000000..c7e938a135 --- /dev/null +++ b/wsmaster/che-core-api-factory/src/test/java/org/eclipse/che/api/factory/server/jpa/FactoryJpaTckRepository.java @@ -0,0 +1,51 @@ +/******************************************************************************* + * Copyright (c) 2012-2016 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 + *******************************************************************************/ +package org.eclipse.che.api.factory.server.jpa; + +import com.google.inject.persist.Transactional; + +import org.eclipse.che.api.factory.server.model.impl.FactoryImpl; +import org.eclipse.che.api.user.server.model.impl.UserImpl; +import org.eclipse.che.commons.test.tck.repository.TckRepository; +import org.eclipse.che.commons.test.tck.repository.TckRepositoryException; + +import javax.inject.Inject; +import javax.inject.Provider; +import javax.persistence.EntityManager; +import java.util.Collection; + +/** + * @author Anton Korneta + */ +@Transactional +public class FactoryJpaTckRepository implements TckRepository { + + @Inject + private Provider managerProvider; + + @Override + public void createAll(Collection factories) throws TckRepositoryException { + final EntityManager manager = managerProvider.get(); + for (FactoryImpl factory : factories) { + final String id = factory.getCreator().getUserId(); + manager.persist(new UserImpl(id, "email_" + id, "name_" + id)); + manager.persist(factory); + } + } + + @Override + public void removeAll() throws TckRepositoryException { + final EntityManager manager = managerProvider.get(); + manager.createQuery("SELECT factory FROM Factory factory", FactoryImpl.class) + .getResultList() + .forEach(manager::remove); + } +} diff --git a/wsmaster/che-core-api-factory/src/test/java/org/eclipse/che/api/factory/server/jpa/JpaTckModule.java b/wsmaster/che-core-api-factory/src/test/java/org/eclipse/che/api/factory/server/jpa/JpaTckModule.java new file mode 100644 index 0000000000..ae0932b0ee --- /dev/null +++ b/wsmaster/che-core-api-factory/src/test/java/org/eclipse/che/api/factory/server/jpa/JpaTckModule.java @@ -0,0 +1,55 @@ +/******************************************************************************* + * Copyright (c) 2012-2016 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 + *******************************************************************************/ +package org.eclipse.che.api.factory.server.jpa; + +import com.google.inject.Singleton; +import com.google.inject.TypeLiteral; +import com.google.inject.persist.jpa.JpaPersistModule; + +import org.eclipse.che.api.core.jdbc.jpa.eclipselink.EntityListenerInjectionManagerInitializer; +import org.eclipse.che.api.core.jdbc.jpa.guice.JpaInitializer; +import org.eclipse.che.api.factory.server.model.impl.FactoryImpl; +import org.eclipse.che.api.factory.server.spi.FactoryDao; +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.model.impl.ProfileImpl; +import org.eclipse.che.api.user.server.model.impl.UserImpl; +import org.eclipse.che.api.user.server.spi.ProfileDao; +import org.eclipse.che.api.user.server.spi.UserDao; +import org.eclipse.che.commons.test.tck.TckModule; +import org.eclipse.che.commons.test.tck.repository.JpaTckRepository; +import org.eclipse.che.commons.test.tck.repository.TckRepository; +import org.eclipse.che.security.PasswordEncryptor; +import org.eclipse.che.security.SHA512PasswordEncryptor; + +/** + * @author Anton Korneta + */ +public class JpaTckModule extends TckModule { + + @Override + protected void configure() { + install(new JpaPersistModule("main")); + bind(JpaInitializer.class).asEagerSingleton(); + bind(EntityListenerInjectionManagerInitializer.class).asEagerSingleton(); + + bind(new TypeLiteral>() {}).toInstance(new JpaTckRepository<>(UserImpl.class)); + bind(new TypeLiteral>() {}).toInstance(new JpaTckRepository<>(FactoryImpl.class)); + bind(new TypeLiteral>() {}).toInstance(new JpaTckRepository<>(ProfileImpl.class)); + + + bind(UserDao.class).to(JpaUserDao.class); + bind(ProfileDao.class).to(JpaProfileDao.class); + bind(FactoryDao.class).to(JpaFactoryDao.class); + + bind(PasswordEncryptor.class).to(SHA512PasswordEncryptor.class).in(Singleton.class); + } +} diff --git a/wsmaster/che-core-api-factory/src/test/java/org/eclipse/che/api/factory/server/spi/tck/FactoryDaoTest.java b/wsmaster/che-core-api-factory/src/test/java/org/eclipse/che/api/factory/server/spi/tck/FactoryDaoTest.java new file mode 100644 index 0000000000..daaeb29425 --- /dev/null +++ b/wsmaster/che-core-api-factory/src/test/java/org/eclipse/che/api/factory/server/spi/tck/FactoryDaoTest.java @@ -0,0 +1,368 @@ +/******************************************************************************* + * Copyright (c) 2012-2016 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 + *******************************************************************************/ +package org.eclipse.che.api.factory.server.spi.tck; + +import com.google.common.collect.ImmutableList; +import com.google.common.collect.ImmutableMap; +import com.google.common.collect.ImmutableSet; + +import org.eclipse.che.api.core.ConflictException; +import org.eclipse.che.api.core.NotFoundException; +import org.eclipse.che.api.core.model.factory.Button; +import org.eclipse.che.api.factory.server.FactoryImage; +import org.eclipse.che.api.factory.server.model.impl.ActionImpl; +import org.eclipse.che.api.factory.server.model.impl.AuthorImpl; +import org.eclipse.che.api.factory.server.model.impl.ButtonAttributesImpl; +import org.eclipse.che.api.factory.server.model.impl.ButtonImpl; +import org.eclipse.che.api.factory.server.model.impl.FactoryImpl; +import org.eclipse.che.api.factory.server.model.impl.IdeImpl; +import org.eclipse.che.api.factory.server.model.impl.OnAppClosedImpl; +import org.eclipse.che.api.factory.server.model.impl.OnAppLoadedImpl; +import org.eclipse.che.api.factory.server.model.impl.OnProjectsLoadedImpl; +import org.eclipse.che.api.factory.server.model.impl.PoliciesImpl; +import org.eclipse.che.api.factory.server.spi.FactoryDao; +import org.eclipse.che.api.machine.server.model.impl.CommandImpl; +import org.eclipse.che.api.user.server.model.impl.UserImpl; +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.commons.lang.Pair; +import org.eclipse.che.commons.test.tck.TckModuleFactory; +import org.eclipse.che.commons.test.tck.repository.TckRepository; +import org.testng.annotations.AfterMethod; +import org.testng.annotations.BeforeMethod; +import org.testng.annotations.Guice; +import org.testng.annotations.Test; + +import javax.inject.Inject; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; + +import static java.util.Arrays.asList; +import static java.util.Collections.emptyList; +import static java.util.Collections.singletonList; +import static java.util.Collections.singletonMap; +import static org.testng.Assert.assertEquals; + +/** + * Tests {@link FactoryDao} contract. + * + * @author Anton Korneta + */ +@Guice(moduleFactory = TckModuleFactory.class) +@Test(suiteName = FactoryDaoTest.SUITE_NAME) +public class FactoryDaoTest { + + public static final String SUITE_NAME = "FactoryDaoTck"; + + private static final int ENTRY_COUNT = 5; + + private FactoryImpl[] factories; + private UserImpl[] users; + + @Inject + private FactoryDao factoryDao; + + @Inject + private TckRepository factoryTckRepository; + + @Inject + private TckRepository userTckRepository; + + @BeforeMethod + public void setUp() throws Exception { + factories = new FactoryImpl[ENTRY_COUNT]; + users = new UserImpl[ENTRY_COUNT]; + for (int i = 0; i < ENTRY_COUNT; i++) { + users[i] = new UserImpl("userId_" + i, "email_" + i, "name" + i); + } + for (int i = 0; i < ENTRY_COUNT; i++) { + factories[i] = createFactory(i, users[i].getId()); + } + userTckRepository.createAll(asList(users)); + factoryTckRepository.createAll(asList(factories)); + } + + @AfterMethod + public void cleanUp() throws Exception { + factoryTckRepository.removeAll(); + userTckRepository.removeAll(); + } + + @Test(dependsOnMethods = "shouldGetFactoryById") + public void shouldCreateFactory() throws Exception { + final FactoryImpl factory = createFactory(10, users[0].getId()); + factory.getCreator().setUserId(factories[0].getCreator().getUserId()); + factoryDao.create(factory); + + assertEquals(factoryDao.getById(factory.getId()), factory); + } + + @Test(expectedExceptions = NullPointerException.class) + public void shouldThrowNpeWhenCreateNullFactory() throws Exception { + factoryDao.create(null); + } + + @Test(expectedExceptions = ConflictException.class) + public void shouldThrowConflictExceptionWhenCreatingFactoryWithExistingId() throws Exception { + final FactoryImpl factory = createFactory(10, users[0].getId()); + final FactoryImpl existing = factories[0]; + factory.getCreator().setUserId(existing.getCreator().getUserId()); + factory.setId(existing.getId()); + factoryDao.create(factory); + } + + // TODO fix after issue: https://github.com/eclipse/che/issues/2110 +// @Test(expectedExceptions = ConflictException.class) +// public void shouldThrowConflictExceptionWhenCreatingFactoryWithExistingNameAndUserId() throws Exception { +// final FactoryImpl factory = createFactory(10, users[0].getId()); +// final FactoryImpl existing = factories[0]; +// factory.getCreator().setUserId(existing.getCreator().getUserId()); +// factory.setName(existing.getName()); +// factoryDao.create(factory); +// } + + @Test + public void shouldUpdateFactory() throws Exception { + final FactoryImpl update = factories[0]; + final String userId = update.getCreator().getUserId(); + update.setName("new-name"); + update.setV("5_0"); + final long currentTime = System.currentTimeMillis(); + update.setPolicies(new PoliciesImpl("ref", "match", "per-click", currentTime, currentTime + 1000)); + update.setCreator(new AuthorImpl(userId, currentTime)); + update.setButton(new ButtonImpl(new ButtonAttributesImpl("green", "icon", "opacity 0.9", true), + Button.Type.NOLOGO)); + update.getIde().getOnAppClosed().getActions().add(new ActionImpl("remove file", ImmutableMap.of("file1", "/che/core/pom.xml"))); + update.getIde().getOnAppLoaded().getActions().add(new ActionImpl("edit file", ImmutableMap.of("file2", "/che/core/pom.xml"))); + update.getIde().getOnProjectsLoaded().getActions().add(new ActionImpl("open file", ImmutableMap.of("file2", "/che/pom.xml"))); + factoryDao.update(update); + + assertEquals(factoryDao.getById(update.getId()), update); + } + +// TODO fix after issue: https://github.com/eclipse/che/issues/2110 +// @Test(expectedExceptions = ConflictException.class) +// public void shouldThrowConflictExceptionWhenUpdateFactoryWithExistingNameAndUserId() throws Exception { +// final FactoryImpl update = factories[0]; +// update.setName(factories[1].getName()); +// update.getCreator().setUserId(factories[1].getCreator().getUserId()); +// factoryDao.update(update); +// } + + @Test(expectedExceptions = NullPointerException.class) + public void shouldThrowNpeWhenFactoryUpdateIsNull() throws Exception { + factoryDao.update(null); + } + + @Test(expectedExceptions = NotFoundException.class) + public void shouldThrowNotFoundExceptionWhenUpdatingNonExistingFactory() throws Exception { + factoryDao.update(createFactory(10, users[0].getId())); + } + + @Test + public void shouldGetFactoryById() throws Exception { + final FactoryImpl factory = factories[0]; + + assertEquals(factoryDao.getById(factory.getId()), factory); + } + + @Test(expectedExceptions = NullPointerException.class) + public void shouldThrowNpeWhenGettingFactoryByNullId() throws Exception { + factoryDao.getById(null); + } + + @Test(expectedExceptions = NotFoundException.class) + public void shouldThrowNotFoundExceptionWhenFactoryWithGivenIdDoesNotExist() throws Exception { + factoryDao.getById("non-existing"); + } + + @Test + public void shouldGetFactoryByIdAttribute() throws Exception { + final FactoryImpl factory = factories[0]; + final List> attributes = ImmutableList.of(Pair.of("id", factory.getId())); + final List result = factoryDao.getByAttribute(1, 0, attributes); + + assertEquals(new HashSet<>(result), ImmutableSet.of(factory)); + } + + @Test(dependsOnMethods = "shouldUpdateFactory") + public void shouldFindFactoryByEmbeddedAttributes() throws Exception { + final List> attributes = ImmutableList.of(Pair.of("policies.match", "match"), + Pair.of("policies.create", "perClick"), + Pair.of("workspace.defaultEnv", "env1")); + final FactoryImpl factory1 = factories[1]; + final FactoryImpl factory3 = factories[3]; + factory1.getPolicies().setCreate("perAccount"); + factory3.getPolicies().setMatch("update"); + factoryDao.update(factory1); + factoryDao.update(factory3); + final List result = factoryDao.getByAttribute(factories.length, 0, attributes); + + assertEquals(new HashSet<>(result), ImmutableSet.of(factories[0], factories[2], factories[4])); + } + + @Test + public void shouldFindAllFactoriesWhenAttributesNotSpecified() throws Exception { + final List> attributes = emptyList(); + final List result = factoryDao.getByAttribute(factories.length, 0, attributes); + + assertEquals(new HashSet<>(result), new HashSet<>(asList(factories))); + } + + @Test(expectedExceptions = NotFoundException.class, dependsOnMethods = "shouldGetFactoryById") + public void shouldRemoveFactory() throws Exception { + final String factoryId = factories[0].getId(); + factoryDao.remove(factoryId); + factoryDao.getById(factoryId); + } + + @Test(expectedExceptions = NullPointerException.class) + public void shouldThrowNpeWhenRemovingNullFactory() throws Exception { + factoryDao.remove(null); + } + + @Test + public void shouldDoNothingWhenRemovingNonExistingFactory() throws Exception { + factoryDao.remove("non-existing"); + } + + private static FactoryImpl createFactory(int index, String userId) { + final long timeMs = System.currentTimeMillis(); + final ButtonImpl factoryButton = new ButtonImpl(new ButtonAttributesImpl("red", "logo", "style", true), + Button.Type.LOGO); + final AuthorImpl creator = new AuthorImpl(userId, timeMs); + final PoliciesImpl policies = new PoliciesImpl("referrer", "match", "perClick", timeMs, timeMs + 1000); + final Set images = new HashSet<>(); + final List a1 = new ArrayList<>(singletonList(new ActionImpl("id" + index, ImmutableMap.of("key1", "value1")))); + final OnAppLoadedImpl onAppLoaded = new OnAppLoadedImpl(a1); + final List a2 = new ArrayList<>(singletonList(new ActionImpl("id" + index, ImmutableMap.of("key2", "value2")))); + final OnProjectsLoadedImpl onProjectsLoaded = new OnProjectsLoadedImpl(a2); + final List a3 = new ArrayList<>(singletonList(new ActionImpl("id" + index, ImmutableMap.of("key3", "value3")))); + final OnAppClosedImpl onAppClosed = new OnAppClosedImpl(a3); + final IdeImpl ide = new IdeImpl(onAppLoaded, onProjectsLoaded, onAppClosed); + return FactoryImpl.builder() + .generateId() + .setVersion("4_0") + .setName("factoryName" + index) + .setWorkspace(createWorkspaceConfig(index)) + .setButton(factoryButton) + .setCreator(creator) + .setPolicies(policies) + .setImages(images) + .setIde(ide) + .build(); + } + + public static WorkspaceConfigImpl createWorkspaceConfig(int index) { + // Project Sources configuration + final SourceStorageImpl source1 = new SourceStorageImpl(); + source1.setType("type1"); + source1.setLocation("location1"); + source1.setParameters(new HashMap<>(ImmutableMap.of("param1", "value1"))); + final SourceStorageImpl source2 = new SourceStorageImpl(); + source2.setType("type2"); + source2.setLocation("location2"); + source2.setParameters(new HashMap<>(ImmutableMap.of("param4", "value1"))); + + // Project Configuration + final ProjectConfigImpl pCfg1 = new ProjectConfigImpl(); + pCfg1.setPath("/path1"); + pCfg1.setType("type1"); + pCfg1.setName("project1"); + pCfg1.setDescription("description1"); + pCfg1.getMixins().addAll(asList("mixin1", "mixin2")); + pCfg1.setSource(source1); + pCfg1.getAttributes().putAll(ImmutableMap.of("key1", asList("v1", "v2"), "key2", asList("v1", "v2"))); + + final ProjectConfigImpl pCfg2 = new ProjectConfigImpl(); + pCfg2.setPath("/path2"); + pCfg2.setType("type2"); + pCfg2.setName("project2"); + pCfg2.setDescription("description2"); + pCfg2.getMixins().addAll(asList("mixin3", "mixin4")); + pCfg2.setSource(source2); + pCfg2.getAttributes().putAll(ImmutableMap.of("key3", asList("v1", "v2"), "key4", asList("v1", "v2"))); + + final List projects = new ArrayList<>(asList(pCfg1, pCfg2)); + + // Commands + final CommandImpl cmd1 = new CommandImpl("name1", "cmd1", "type1"); + cmd1.getAttributes().putAll(ImmutableMap.of("key1", "value1")); + final CommandImpl cmd2 = new CommandImpl("name2", "cmd2", "type2"); + cmd2.getAttributes().putAll(ImmutableMap.of("key4", "value4")); + final List commands = new ArrayList<>(asList(cmd1, cmd2)); + + // Machine configs + final ExtendedMachineImpl exMachine1 = new ExtendedMachineImpl(); + final ServerConf2Impl serverConf1 = new ServerConf2Impl("2265", "http", singletonMap("prop1", "val")); + final ServerConf2Impl serverConf2 = new ServerConf2Impl("2266", "ftp", singletonMap("prop1", "val")); + exMachine1.setServers(ImmutableMap.of("ref1", serverConf1, "ref2", serverConf2)); + exMachine1.setAgents(ImmutableList.of("agent5", "agent4")); + exMachine1.setAttributes(singletonMap("att1", "val")); + + final ExtendedMachineImpl exMachine2 = new ExtendedMachineImpl(); + final ServerConf2Impl serverConf3 = new ServerConf2Impl("2333", "https", singletonMap("prop2", "val")); + final ServerConf2Impl serverConf4 = new ServerConf2Impl("2334", "wss", singletonMap("prop2", "val")); + exMachine2.setServers(ImmutableMap.of("ref1", serverConf3, "ref2", serverConf4)); + exMachine2.setAgents(ImmutableList.of("agent2", "agent1")); + exMachine2.setAttributes(singletonMap("att1", "val")); + + final ExtendedMachineImpl exMachine3 = new ExtendedMachineImpl(); + final ServerConf2Impl serverConf5 = new ServerConf2Impl("2333", "https", singletonMap("prop2", "val")); + exMachine3.setServers(singletonMap("ref1", serverConf5)); + exMachine3.setAgents(ImmutableList.of("agent6", "agent2")); + exMachine3.setAttributes(singletonMap("att1", "val")); + + + // Environments + final EnvironmentRecipeImpl recipe1 = new EnvironmentRecipeImpl(); + recipe1.setLocation("https://eclipse.che/Dockerfile"); + recipe1.setType("dockerfile"); + recipe1.setContentType("text/x-dockerfile"); + recipe1.setContent("content"); + final EnvironmentImpl env1 = new EnvironmentImpl(); + env1.setMachines(new HashMap<>(ImmutableMap.of("machine1", exMachine1, + "machine2", exMachine2, + "machine3", exMachine3))); + env1.setRecipe(recipe1); + + final EnvironmentRecipeImpl recipe2 = new EnvironmentRecipeImpl(); + recipe2.setLocation("https://eclipse.che/Dockerfile"); + recipe2.setType("dockerfile"); + recipe2.setContentType("text/x-dockerfile"); + recipe2.setContent("content"); + final EnvironmentImpl env2 = new EnvironmentImpl(); + env2.setMachines(new HashMap<>(ImmutableMap.of("machine1", exMachine1, + "machine3", exMachine3))); + env2.setRecipe(recipe2); + + final Map environments = ImmutableMap.of("env1", env1, "env2", env2); + + // Workspace configuration + final WorkspaceConfigImpl wCfg = new WorkspaceConfigImpl(); + wCfg.setDefaultEnv("env1"); + wCfg.setName("cfgName_" + index); + wCfg.setDescription("description"); + wCfg.setCommands(commands); + wCfg.setProjects(projects); + wCfg.setEnvironments(environments); + return wCfg; + } +} diff --git a/wsmaster/che-core-api-factory/src/test/resources/META-INF/persistence.xml b/wsmaster/che-core-api-factory/src/test/resources/META-INF/persistence.xml new file mode 100644 index 0000000000..839c2acc49 --- /dev/null +++ b/wsmaster/che-core-api-factory/src/test/resources/META-INF/persistence.xml @@ -0,0 +1,58 @@ + + + + org.eclipse.che.api.factory.server.model.impl.ActionImpl + org.eclipse.che.api.factory.server.model.impl.AuthorImpl + org.eclipse.che.api.factory.server.model.impl.ButtonAttributesImpl + org.eclipse.che.api.factory.server.model.impl.ButtonImpl + org.eclipse.che.api.factory.server.model.impl.FactoryImpl + org.eclipse.che.api.factory.server.model.impl.IdeImpl + org.eclipse.che.api.factory.server.model.impl.OnAppClosedImpl + org.eclipse.che.api.factory.server.model.impl.OnProjectsLoadedImpl + org.eclipse.che.api.factory.server.model.impl.OnAppLoadedImpl + org.eclipse.che.api.factory.server.model.impl.PoliciesImpl + org.eclipse.che.api.factory.server.FactoryImage + org.eclipse.che.account.spi.AccountImpl + org.eclipse.che.api.user.server.model.impl.UserImpl + + org.eclipse.che.api.workspace.server.model.impl.WorkspaceConfigImpl + org.eclipse.che.api.workspace.server.model.impl.ProjectConfigImpl + org.eclipse.che.api.workspace.server.model.impl.EnvironmentImpl + org.eclipse.che.api.workspace.server.model.impl.EnvironmentRecipeImpl + org.eclipse.che.api.workspace.server.model.impl.ExtendedMachineImpl + org.eclipse.che.api.workspace.server.model.impl.ProjectConfigImpl$Attribute + org.eclipse.che.api.workspace.server.model.impl.SourceStorageImpl + org.eclipse.che.api.workspace.server.model.impl.ServerConf2Impl + org.eclipse.che.api.workspace.server.model.impl.stack.StackImpl + + org.eclipse.che.api.machine.server.model.impl.CommandImpl + org.eclipse.che.api.machine.server.recipe.RecipeImpl + true + + + + + + + + + + + + + + + diff --git a/wsmaster/che-core-api-factory/src/test/resources/META-INF/services/org.eclipse.che.commons.test.tck.TckModule b/wsmaster/che-core-api-factory/src/test/resources/META-INF/services/org.eclipse.che.commons.test.tck.TckModule new file mode 100644 index 0000000000..a2e5fdb60c --- /dev/null +++ b/wsmaster/che-core-api-factory/src/test/resources/META-INF/services/org.eclipse.che.commons.test.tck.TckModule @@ -0,0 +1 @@ +org.eclipse.che.api.factory.server.jpa.JpaTckModule diff --git a/wsmaster/che-core-api-machine-shared/src/main/java/org/eclipse/che/api/machine/shared/dto/SnapshotDto.java b/wsmaster/che-core-api-machine-shared/src/main/java/org/eclipse/che/api/machine/shared/dto/SnapshotDto.java index 7e2677bf0c..a6c2ee4e4e 100644 --- a/wsmaster/che-core-api-machine-shared/src/main/java/org/eclipse/che/api/machine/shared/dto/SnapshotDto.java +++ b/wsmaster/che-core-api-machine-shared/src/main/java/org/eclipse/che/api/machine/shared/dto/SnapshotDto.java @@ -26,10 +26,6 @@ public interface SnapshotDto extends Snapshot, Hyperlinks { SnapshotDto withId(String id); - void setNamespace(String owner); - - SnapshotDto withNamespace(String namespace); - void setType(String type); SnapshotDto withType(String type); diff --git a/wsmaster/che-core-api-machine/pom.xml b/wsmaster/che-core-api-machine/pom.xml index 4702fd5554..178b418d3a 100644 --- a/wsmaster/che-core-api-machine/pom.xml +++ b/wsmaster/che-core-api-machine/pom.xml @@ -85,6 +85,11 @@ org.slf4j slf4j-api + + com.google.inject.extensions + guice-persist + provided + javax.servlet javax.servlet-api @@ -95,17 +100,42 @@ javax.websocket-api provided + + org.eclipse.che.core + che-core-api-jdbc + provided + + + org.eclipse.persistence + javax.persistence + provided + ch.qos.logback logback-classic test + + com.h2database + h2 + test + com.jayway.restassured rest-assured test + + org.eclipse.che.core + che-core-api-jdbc-vendor-h2 + test + + + org.eclipse.che.core + che-core-api-user + test + org.eclipse.che.core che-core-commons-test @@ -116,6 +146,11 @@ jetty-server test + + org.eclipse.persistence + eclipselink + test + org.everrest everrest-assured @@ -152,4 +187,24 @@ test + + + + org.apache.maven.plugins + maven-jar-plugin + + + + test-jar + + + + **/spi/tck/*.* + + + + + + + diff --git a/wsmaster/che-core-api-machine/src/main/java/org/eclipse/che/api/machine/server/DtoConverter.java b/wsmaster/che-core-api-machine/src/main/java/org/eclipse/che/api/machine/server/DtoConverter.java index fb90b493b5..7ddb71e135 100644 --- a/wsmaster/che-core-api-machine/src/main/java/org/eclipse/che/api/machine/server/DtoConverter.java +++ b/wsmaster/che-core-api-machine/src/main/java/org/eclipse/che/api/machine/server/DtoConverter.java @@ -131,7 +131,6 @@ public final class DtoConverter { .withCreationDate(snapshot.getCreationDate()) .withDev(snapshot.isDev()) .withId(snapshot.getId()) - .withNamespace(snapshot.getNamespace()) .withWorkspaceId(snapshot.getWorkspaceId()) .withLinks(null); } diff --git a/wsmaster/che-core-api-machine/src/main/java/org/eclipse/che/api/machine/server/event/BeforeRecipeRemovedEvent.java b/wsmaster/che-core-api-machine/src/main/java/org/eclipse/che/api/machine/server/event/BeforeRecipeRemovedEvent.java new file mode 100644 index 0000000000..a9bf42cd94 --- /dev/null +++ b/wsmaster/che-core-api-machine/src/main/java/org/eclipse/che/api/machine/server/event/BeforeRecipeRemovedEvent.java @@ -0,0 +1,30 @@ +/******************************************************************************* + * Copyright (c) 2012-2016 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 + *******************************************************************************/ +package org.eclipse.che.api.machine.server.event; + +import org.eclipse.che.api.machine.server.recipe.RecipeImpl; + +/** + * Pre-removal event of {@link RecipeImpl}. + * + * @author Max Shaposhnik + */ +public class BeforeRecipeRemovedEvent { + private final RecipeImpl recipe; + + public BeforeRecipeRemovedEvent(RecipeImpl recipe) { + this.recipe = recipe; + } + + public RecipeImpl getRecipe() { + return recipe; + } +} diff --git a/wsmaster/che-core-api-machine/src/main/java/org/eclipse/che/api/machine/server/jpa/JpaRecipeDao.java b/wsmaster/che-core-api-machine/src/main/java/org/eclipse/che/api/machine/server/jpa/JpaRecipeDao.java new file mode 100644 index 0000000000..afb551409a --- /dev/null +++ b/wsmaster/che-core-api-machine/src/main/java/org/eclipse/che/api/machine/server/jpa/JpaRecipeDao.java @@ -0,0 +1,159 @@ +/******************************************************************************* + * Copyright (c) 2012-2016 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 + *******************************************************************************/ +package org.eclipse.che.api.machine.server.jpa; + +import com.google.inject.persist.Transactional; + +import org.eclipse.che.api.core.ConflictException; +import org.eclipse.che.api.core.NotFoundException; +import org.eclipse.che.api.core.ServerException; +import org.eclipse.che.api.core.jdbc.jpa.DuplicateKeyException; +import org.eclipse.che.api.core.jdbc.jpa.IntegrityConstraintViolationException; +import org.eclipse.che.api.machine.server.recipe.RecipeImpl; +import org.eclipse.che.api.machine.server.spi.RecipeDao; + +import javax.inject.Inject; +import javax.inject.Provider; +import javax.inject.Singleton; +import javax.persistence.EntityManager; +import javax.persistence.TypedQuery; +import javax.persistence.criteria.CriteriaBuilder; +import javax.persistence.criteria.CriteriaQuery; +import javax.persistence.criteria.Join; +import javax.persistence.criteria.ParameterExpression; +import javax.persistence.criteria.Predicate; +import javax.persistence.criteria.Root; +import java.util.List; + +import static java.lang.String.format; +import static java.util.Objects.requireNonNull; + +/** + * Implementation of {@link RecipeDao}. + * + * @author Anton Korneta + */ +@Singleton +public class JpaRecipeDao implements RecipeDao { + + @Inject + private Provider managerProvider; + + @Override + public void create(RecipeImpl recipe) throws ConflictException, ServerException { + requireNonNull(recipe); + try { + doCreateRecipe(recipe); + } catch (DuplicateKeyException ex) { + throw new ConflictException(format("Recipe with id %s already exists", recipe.getId())); + } catch (IntegrityConstraintViolationException ex) { + throw new ConflictException("Could not create recipe with permissions for non-existent user"); + } catch (RuntimeException ex) { + throw new ServerException(ex.getLocalizedMessage(), ex); + } + } + + @Override + public RecipeImpl update(RecipeImpl update) throws NotFoundException, ServerException { + requireNonNull(update); + try { + return doUpdate(update); + } catch (RuntimeException ex) { + throw new ServerException(ex.getLocalizedMessage(), ex); + } + } + + @Override + public void remove(String id) throws ServerException { + requireNonNull(id); + try { + doRemove(id); + } catch (RuntimeException x) { + throw new ServerException(x.getLocalizedMessage(), x); + } + } + + @Override + @Transactional + public RecipeImpl getById(String id) throws NotFoundException, ServerException { + requireNonNull(id); + + try { + final EntityManager manager = managerProvider.get(); + final RecipeImpl recipe = manager.find(RecipeImpl.class, id); + if (recipe == null) { + throw new NotFoundException(format("Recipe with id '%s' doesn't exist", id)); + } + return recipe; + } catch (RuntimeException ex) { + throw new ServerException(ex.getLocalizedMessage(), ex); + } + } + + @Override + @Transactional + public List search(String user, + List tags, + String type, + int skipCount, + int maxItems) throws ServerException { + try { + final EntityManager manager = managerProvider.get(); + final CriteriaBuilder cb = manager.getCriteriaBuilder(); + final CriteriaQuery query = cb.createQuery(RecipeImpl.class); + final Root fromRecipe = query.from(RecipeImpl.class); + final ParameterExpression typeParam = cb.parameter(String.class, "recipeType"); + final Predicate checkType = cb.or(cb.isNull(typeParam), + cb.equal(fromRecipe.get("type"), typeParam)); + final TypedQuery typedQuery; + if (tags != null && !tags.isEmpty()) { + final Join tag = fromRecipe.join("tags"); + query.select(cb.construct(RecipeImpl.class, tag.getParent())) + .where(cb.and(checkType, tag.in(tags))) + .groupBy(fromRecipe.get("id")) + .having(cb.equal(cb.count(tag), tags.size())); + typedQuery = manager.createQuery(query) + .setParameter("tags", tags); + } else { + typedQuery = manager.createQuery(query.where(checkType)); + } + return typedQuery.setParameter("recipeType", type) + .setFirstResult(skipCount) + .setMaxResults(maxItems) + .getResultList(); + } catch (RuntimeException ex) { + throw new ServerException(ex.getLocalizedMessage(), ex); + } + } + + @Transactional + protected void doRemove(String id) { + final EntityManager manager = managerProvider.get(); + final RecipeImpl recipe = manager.find(RecipeImpl.class, id); + if (recipe != null) { + manager.remove(recipe); + } + } + + @Transactional + protected RecipeImpl doUpdate(RecipeImpl update) throws NotFoundException { + final EntityManager manager = managerProvider.get(); + if (manager.find(RecipeImpl.class, update.getId()) == null) { + throw new NotFoundException(format("Could not update recipe with id %s because it doesn't exist", update.getId())); + } + return manager.merge(update); + } + + @Transactional + protected void doCreateRecipe(RecipeImpl recipe) { + managerProvider.get().persist(recipe); + } +} diff --git a/wsmaster/che-core-api-machine/src/main/java/org/eclipse/che/api/machine/server/jpa/JpaSnapshotDao.java b/wsmaster/che-core-api-machine/src/main/java/org/eclipse/che/api/machine/server/jpa/JpaSnapshotDao.java new file mode 100644 index 0000000000..0cd1f7407c --- /dev/null +++ b/wsmaster/che-core-api-machine/src/main/java/org/eclipse/che/api/machine/server/jpa/JpaSnapshotDao.java @@ -0,0 +1,134 @@ +/******************************************************************************* + * Copyright (c) 2012-2016 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 + *******************************************************************************/ +package org.eclipse.che.api.machine.server.jpa; + +import com.google.inject.persist.Transactional; + +import org.eclipse.che.api.core.NotFoundException; +import org.eclipse.che.api.core.jdbc.jpa.DuplicateKeyException; +import org.eclipse.che.api.machine.server.exception.SnapshotException; +import org.eclipse.che.api.machine.server.model.impl.SnapshotImpl; +import org.eclipse.che.api.machine.server.spi.SnapshotDao; + +import javax.inject.Inject; +import javax.inject.Provider; +import javax.inject.Singleton; +import javax.persistence.EntityManager; +import javax.persistence.NoResultException; +import java.util.List; + +import static java.lang.String.format; +import static java.util.Objects.requireNonNull; + +/** + * JPA based {@link SnapshotDao} implementation. + * + * @author Yevhenii Voevodin + */ +@Singleton +public class JpaSnapshotDao implements SnapshotDao { + + @Inject + private Provider managerProvider; + + @Override + @Transactional + public SnapshotImpl getSnapshot(String snapshotId) throws NotFoundException, SnapshotException { + requireNonNull(snapshotId, "Required non-null snapshotId"); + try { + final SnapshotImpl snapshot = managerProvider.get().find(SnapshotImpl.class, snapshotId); + if (snapshot == null) { + throw new NotFoundException(format("Snapshot with id '%s' doesn't exist", snapshotId)); + } + return snapshot; + } catch (RuntimeException x) { + throw new SnapshotException(x.getLocalizedMessage(), x); + } + } + + @Override + @Transactional + public SnapshotImpl getSnapshot(String workspaceId, String envName, String machineName) throws NotFoundException, SnapshotException { + requireNonNull(workspaceId, "Required non-null workspace id"); + requireNonNull(envName, "Required non-null environment name"); + requireNonNull(machineName, "Required non-null machine name"); + try { + return managerProvider.get() + .createNamedQuery("Snapshot.getByMachine", SnapshotImpl.class) + .setParameter("workspaceId", workspaceId) + .setParameter("envName", envName) + .setParameter("machineName", machineName) + .getSingleResult(); + } catch (NoResultException x) { + throw new NotFoundException(format("Snapshot for machine '%s:%s:%s' doesn't exist", + workspaceId, + envName, + machineName)); + } catch (RuntimeException x) { + throw new SnapshotException(x.getLocalizedMessage(), x); + } + } + + @Override + @Transactional + public List findSnapshots(String workspaceId) throws SnapshotException { + requireNonNull(workspaceId, "Required non-null workspace id"); + try { + return managerProvider.get() + .createNamedQuery("Snapshot.findSnapshots", SnapshotImpl.class) + .setParameter("workspaceId", workspaceId) + .getResultList(); + } catch (RuntimeException x) { + throw new SnapshotException(x.getLocalizedMessage(), x); + } + } + + @Override + public void saveSnapshot(SnapshotImpl snapshot) throws SnapshotException { + requireNonNull(snapshot, "Required non-null snapshot"); + try { + doSave(snapshot); + } catch (DuplicateKeyException x) { + throw new SnapshotException(format("Snapshot with id '%s' or for machine '%s:%s:%s' already exists", + snapshot.getId(), + snapshot.getWorkspaceId(), + snapshot.getEnvName(), + snapshot.getMachineName())); + } catch (RuntimeException x) { + throw new SnapshotException(x.getLocalizedMessage(), x); + } + } + + @Override + public void removeSnapshot(String snapshotId) throws NotFoundException, SnapshotException { + requireNonNull(snapshotId, "Required non-null snapshot id"); + try { + doRemove(snapshotId); + } catch (RuntimeException x) { + throw new SnapshotException(x.getLocalizedMessage(), x); + } + } + + @Transactional + protected void doSave(SnapshotImpl snapshot) { + managerProvider.get().persist(snapshot); + } + + @Transactional + protected void doRemove(String snapshotId) throws NotFoundException { + final EntityManager manager = managerProvider.get(); + final SnapshotImpl snapshot = manager.find(SnapshotImpl.class, snapshotId); + if (snapshot == null) { + throw new NotFoundException(format("Snapshot with id '%s' doesn't exist", snapshotId)); + } + manager.remove(snapshot); + } +} diff --git a/wsmaster/che-core-api-machine/src/main/java/org/eclipse/che/api/machine/server/jpa/MachineJpaModule.java b/wsmaster/che-core-api-machine/src/main/java/org/eclipse/che/api/machine/server/jpa/MachineJpaModule.java new file mode 100644 index 0000000000..6680f905c9 --- /dev/null +++ b/wsmaster/che-core-api-machine/src/main/java/org/eclipse/che/api/machine/server/jpa/MachineJpaModule.java @@ -0,0 +1,28 @@ +/******************************************************************************* + * Copyright (c) 2012-2016 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 + *******************************************************************************/ +package org.eclipse.che.api.machine.server.jpa; + +import com.google.inject.AbstractModule; + +import org.eclipse.che.api.machine.server.spi.RecipeDao; +import org.eclipse.che.api.machine.server.spi.SnapshotDao; + +/** + * @author Yevhenii Voevodin + */ +public class MachineJpaModule extends AbstractModule { + + @Override + protected void configure() { + bind(RecipeDao.class).to(JpaRecipeDao.class); + bind(SnapshotDao.class).to(JpaSnapshotDao.class); + } +} diff --git a/wsmaster/che-core-api-machine/src/main/java/org/eclipse/che/api/machine/server/jpa/RecipeEntityListener.java b/wsmaster/che-core-api-machine/src/main/java/org/eclipse/che/api/machine/server/jpa/RecipeEntityListener.java new file mode 100644 index 0000000000..7bab8b7205 --- /dev/null +++ b/wsmaster/che-core-api-machine/src/main/java/org/eclipse/che/api/machine/server/jpa/RecipeEntityListener.java @@ -0,0 +1,36 @@ +/******************************************************************************* + * Copyright (c) 2012-2016 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 + *******************************************************************************/ +package org.eclipse.che.api.machine.server.jpa; + +import org.eclipse.che.api.core.notification.EventService; +import org.eclipse.che.api.machine.server.event.BeforeRecipeRemovedEvent; +import org.eclipse.che.api.machine.server.recipe.RecipeImpl; + +import javax.inject.Inject; +import javax.inject.Singleton; +import javax.persistence.PreRemove; + +/** + * Entity events listener for {@link RecipeImpl}. + * + * @author Max Shaposhnik + */ +@Singleton +public class RecipeEntityListener { + + @Inject + private EventService eventService; + + @PreRemove + private void preRemove(RecipeImpl recipe) { + eventService.publish(new BeforeRecipeRemovedEvent(recipe)); + } +} diff --git a/wsmaster/che-core-api-machine/src/main/java/org/eclipse/che/api/machine/server/model/impl/CommandImpl.java b/wsmaster/che-core-api-machine/src/main/java/org/eclipse/che/api/machine/server/model/impl/CommandImpl.java index ac69f7aeef..0216e3d780 100644 --- a/wsmaster/che-core-api-machine/src/main/java/org/eclipse/che/api/machine/server/model/impl/CommandImpl.java +++ b/wsmaster/che-core-api-machine/src/main/java/org/eclipse/che/api/machine/server/model/impl/CommandImpl.java @@ -10,24 +10,46 @@ *******************************************************************************/ package org.eclipse.che.api.machine.server.model.impl; +import org.eclipse.che.api.core.model.machine.Command; + +import javax.persistence.Column; +import javax.persistence.ElementCollection; +import javax.persistence.Entity; +import javax.persistence.GeneratedValue; +import javax.persistence.Id; +import javax.persistence.MapKeyColumn; import java.util.HashMap; import java.util.Map; import java.util.Objects; -import org.eclipse.che.api.core.model.machine.Command; - /** * Data object for {@link Command}. * * @author Eugene Voevodin */ +@Entity(name = "Command") public class CommandImpl implements Command { - private String name; - private String commandLine; - private String type; + @Id + @GeneratedValue + private Long id; + + @Column(nullable = false) + private String name; + + @Column(nullable = false, columnDefinition = "TEXT") + private String commandLine; + + @Column(nullable = false) + private String type; + + @ElementCollection + @MapKeyColumn(name = "name") + @Column(name = "value", columnDefinition = "TEXT") private Map attributes; + public CommandImpl() {} + public CommandImpl(String name, String commandLine, String type) { this.name = name; this.commandLine = commandLine; diff --git a/wsmaster/che-core-api-machine/src/main/java/org/eclipse/che/api/machine/server/model/impl/MachineConfigImpl.java b/wsmaster/che-core-api-machine/src/main/java/org/eclipse/che/api/machine/server/model/impl/MachineConfigImpl.java index 80ebcbc32e..77246ea777 100644 --- a/wsmaster/che-core-api-machine/src/main/java/org/eclipse/che/api/machine/server/model/impl/MachineConfigImpl.java +++ b/wsmaster/che-core-api-machine/src/main/java/org/eclipse/che/api/machine/server/model/impl/MachineConfigImpl.java @@ -10,8 +10,8 @@ *******************************************************************************/ package org.eclipse.che.api.machine.server.model.impl; -import org.eclipse.che.api.core.model.machine.MachineLimits; import org.eclipse.che.api.core.model.machine.MachineConfig; +import org.eclipse.che.api.core.model.machine.MachineLimits; import org.eclipse.che.api.core.model.machine.MachineSource; import org.eclipse.che.api.core.model.machine.ServerConf; @@ -91,8 +91,8 @@ public class MachineConfigImpl implements MachineConfig { return source; } - public void setSource(MachineSource machineSource) { - this.source = new MachineSourceImpl(machineSource); + public void setSource(MachineSourceImpl machineSource) { + this.source = machineSource; } @Override @@ -100,16 +100,28 @@ public class MachineConfigImpl implements MachineConfig { return dev; } + public void setDev(boolean dev) { + this.dev = dev; + } + @Override public String getType() { return type; } + public void setType(String type) { + this.type = type; + } + @Override public MachineLimitsImpl getLimits() { return limits; } + public void setLimits(MachineLimits machineLimits) { + this.limits = new MachineLimitsImpl(machineLimits); + } + @Override public List getServers() { if (servers == null) { @@ -118,6 +130,10 @@ public class MachineConfigImpl implements MachineConfig { return servers; } + public void setServers(List servers) { + this.servers = servers; + } + @Override public Map getEnvVariables() { if (envVariables == null) { @@ -126,8 +142,8 @@ public class MachineConfigImpl implements MachineConfig { return envVariables; } - public void setLimits(MachineLimits machineLimits) { - this.limits = new MachineLimitsImpl(machineLimits); + public void setEnvVariables(Map envVariables) { + this.envVariables = envVariables; } @Override diff --git a/wsmaster/che-core-api-machine/src/main/java/org/eclipse/che/api/machine/server/model/impl/MachineLimitsImpl.java b/wsmaster/che-core-api-machine/src/main/java/org/eclipse/che/api/machine/server/model/impl/MachineLimitsImpl.java index f293fe8337..08cd49fb6e 100644 --- a/wsmaster/che-core-api-machine/src/main/java/org/eclipse/che/api/machine/server/model/impl/MachineLimitsImpl.java +++ b/wsmaster/che-core-api-machine/src/main/java/org/eclipse/che/api/machine/server/model/impl/MachineLimitsImpl.java @@ -14,18 +14,22 @@ import org.eclipse.che.api.core.model.machine.MachineLimits; /** * @author Alexander Garagatyi + * @author Yevhenii Voevodin */ public class MachineLimitsImpl implements MachineLimits { - private final int memory; + + private int memory; public MachineLimitsImpl(MachineLimits machineLimits) { - if(machineLimits != null) { + if (machineLimits != null) { memory = machineLimits.getRam(); } else { memory = 0; } } + public MachineLimitsImpl() {} + public MachineLimitsImpl(int memory) { this.memory = memory; } @@ -35,6 +39,10 @@ public class MachineLimitsImpl implements MachineLimits { return memory; } + public void setRam(int memory) { + this.memory = memory; + } + @Override public boolean equals(Object o) { if (this == o) return true; @@ -50,4 +58,11 @@ public class MachineLimitsImpl implements MachineLimits { public int hashCode() { return memory; } + + @Override + public String toString() { + return "MachineLimitsImpl{" + + "memory=" + memory + + '}'; + } } diff --git a/wsmaster/che-core-api-machine/src/main/java/org/eclipse/che/api/machine/server/model/impl/MachineSourceImpl.java b/wsmaster/che-core-api-machine/src/main/java/org/eclipse/che/api/machine/server/model/impl/MachineSourceImpl.java index 43e215b18d..3f5109b540 100644 --- a/wsmaster/che-core-api-machine/src/main/java/org/eclipse/che/api/machine/server/model/impl/MachineSourceImpl.java +++ b/wsmaster/che-core-api-machine/src/main/java/org/eclipse/che/api/machine/server/model/impl/MachineSourceImpl.java @@ -12,41 +12,42 @@ package org.eclipse.che.api.machine.server.model.impl; import org.eclipse.che.api.core.model.machine.MachineSource; +import javax.persistence.Basic; +import javax.persistence.Column; +import javax.persistence.Embeddable; import java.util.Objects; import static java.util.Objects.hash; -//TODO move? - /** * Data object for {@link MachineSource}. * * @author Eugene Voevodin */ +@Embeddable public class MachineSourceImpl implements MachineSource { + @Column(name = "source_type") private String type; + + @Basic private String location; + + @Column(columnDefinition = "TEXT") private String content; - protected MachineSourceImpl() { - - } - - /** - * Please use {@link MachineSourceImpl with type and then setLocation or setContent} - * @param type the source type defined by implementation. - */ - @Deprecated - public MachineSourceImpl(String type, String location) { - this(type); - setLocation(location); - } + public MachineSourceImpl() {} public MachineSourceImpl(String type) { this.type = type; } + public MachineSourceImpl(String type, String location, String content) { + this.type = type; + this.location = location; + this.content = content; + } + public MachineSourceImpl(MachineSource machineSource) { if (machineSource != null) { this.type = machineSource.getType(); diff --git a/wsmaster/che-core-api-machine/src/main/java/org/eclipse/che/api/machine/server/model/impl/ServerConfImpl.java b/wsmaster/che-core-api-machine/src/main/java/org/eclipse/che/api/machine/server/model/impl/ServerConfImpl.java index 714b798b6a..ef987656ca 100644 --- a/wsmaster/che-core-api-machine/src/main/java/org/eclipse/che/api/machine/server/model/impl/ServerConfImpl.java +++ b/wsmaster/che-core-api-machine/src/main/java/org/eclipse/che/api/machine/server/model/impl/ServerConfImpl.java @@ -12,16 +12,30 @@ package org.eclipse.che.api.machine.server.model.impl; import org.eclipse.che.api.core.model.machine.ServerConf; +import javax.persistence.Basic; +import javax.persistence.Embeddable; import java.util.Objects; /** * @author Alexander Garagatyi + * @author Yevhenii Voevodin */ +@Embeddable public class ServerConfImpl implements ServerConf { - private final String ref; - private final String port; - private final String protocol; - private final String path; + + @Basic + private String ref; + + @Basic + private String port; + + @Basic + private String protocol; + + @Basic + private String path; + + public ServerConfImpl() {} public ServerConfImpl(String ref, String port, String protocol, String path) { this.ref = ref; @@ -42,21 +56,37 @@ public class ServerConfImpl implements ServerConf { return ref; } + public void setRef(String ref) { + this.ref = ref; + } + @Override public String getPort() { return port; } + public void setPort(String port) { + this.port = port; + } + @Override public String getProtocol() { return protocol; } + public void setProtocol(String protocol) { + this.protocol = protocol; + } + @Override public String getPath() { return path; } + public void setPath(String path) { + this.path = path; + } + @Override public boolean equals(Object o) { if (this == o) return true; diff --git a/wsmaster/che-core-api-machine/src/main/java/org/eclipse/che/api/machine/server/model/impl/SnapshotImpl.java b/wsmaster/che-core-api-machine/src/main/java/org/eclipse/che/api/machine/server/model/impl/SnapshotImpl.java index 0e27c07487..36737b0b81 100644 --- a/wsmaster/che-core-api-machine/src/main/java/org/eclipse/che/api/machine/server/model/impl/SnapshotImpl.java +++ b/wsmaster/che-core-api-machine/src/main/java/org/eclipse/che/api/machine/server/model/impl/SnapshotImpl.java @@ -13,40 +13,92 @@ package org.eclipse.che.api.machine.server.model.impl; import org.eclipse.che.api.core.model.machine.MachineConfig; import org.eclipse.che.api.core.model.machine.MachineSource; import org.eclipse.che.api.core.model.machine.Snapshot; +import org.eclipse.che.api.machine.server.spi.Instance; import org.eclipse.che.commons.lang.NameGenerator; +import javax.persistence.Basic; +import javax.persistence.Column; +import javax.persistence.Embedded; +import javax.persistence.Entity; +import javax.persistence.Id; +import javax.persistence.Index; +import javax.persistence.NamedQueries; +import javax.persistence.NamedQuery; +import javax.persistence.Table; import java.util.Objects; -import static java.util.Objects.requireNonNull; - /** - * Saved state of {@link org.eclipse.che.api.machine.server.spi.Instance}. + * Saved state of {@link Instance}. * * @author Yevhenii Voevodin */ +@Entity(name = "Snapshot") +@NamedQueries( + { + @NamedQuery(name = "Snapshot.getByMachine", + query = "SELECT snapshot " + + "FROM Snapshot snapshot " + + "WHERE snapshot.workspaceId = :workspaceId" + + " AND snapshot.envName = :envName" + + " AND snapshot.machineName = :machineName"), + @NamedQuery(name = "Snapshot.findSnapshots", + query = "SELECT snapshot " + + "FROM Snapshot snapshot " + + "WHERE snapshot.workspaceId = :workspaceId") + } +) +@Table(indexes = @Index(columnList = "workspaceId, envName, machineName", unique = true)) public class SnapshotImpl implements Snapshot { public static SnapshotBuilder builder() { return new SnapshotBuilder(); } - private final String workspaceId; - private final String machineName; - private final String envName; - private final String id; - private final String type; - private final String namespace; - private final boolean isDev; - private final long creationDate; + @Id + private String id; - private String description; + @Column(nullable = false) + private String workspaceId; + + @Column(nullable = false) + private String machineName; + + @Column(nullable = false) + private String envName; + + @Basic + private String type; + + @Basic + private boolean isDev; + + @Basic + private long creationDate; + + @Basic + private String description; + + @Embedded private MachineSourceImpl machineSource; + public SnapshotImpl() {} + public SnapshotImpl(Snapshot snapshot) { this(snapshot.getId(), snapshot.getType(), null, - snapshot.getNamespace(), + snapshot.getCreationDate(), + snapshot.getWorkspaceId(), + snapshot.getDescription(), + snapshot.isDev(), + snapshot.getMachineName(), + snapshot.getEnvName()); + } + + public SnapshotImpl(SnapshotImpl snapshot) { + this(snapshot.getId(), + snapshot.getType(), + snapshot.getMachineSource(), snapshot.getCreationDate(), snapshot.getWorkspaceId(), snapshot.getDescription(), @@ -58,19 +110,17 @@ public class SnapshotImpl implements Snapshot { public SnapshotImpl(String id, String type, MachineSource machineSource, - String namespace, long creationDate, String workspaceId, String description, boolean isDev, String machineName, String envName) { - this.id = requireNonNull(id, "Required non-null snapshot id"); - this.type = requireNonNull(type, "Required non-null snapshot type"); - this.namespace = requireNonNull(namespace, "Required non-null snapshot namespace"); - this.workspaceId = requireNonNull(workspaceId, "Required non-null workspace id for snapshot"); - this.machineName = requireNonNull(machineName, "Required non-null snapshot machine name"); - this.envName = requireNonNull(envName, "Required non-null environment name for snapshot"); + this.id = id; + this.type = type; + this.workspaceId = workspaceId; + this.machineName = machineName; + this.envName = envName; this.machineSource = machineSource != null ? new MachineSourceImpl(machineSource) : null; this.description = description; this.isDev = isDev; @@ -82,18 +132,25 @@ public class SnapshotImpl implements Snapshot { return id; } + public void setId(String id) { + this.id = id; + } + @Override public String getType() { return type; } + public void setType(String type) { + this.type = type; + } + public MachineSourceImpl getMachineSource() { return machineSource; } - @Override - public String getNamespace() { - return namespace; + public void setMachineSource(MachineSourceImpl machineSource) { + this.machineSource = machineSource; } @Override @@ -101,37 +158,53 @@ public class SnapshotImpl implements Snapshot { return creationDate; } + public void setCreationDate(long creationDate) { + this.creationDate = creationDate; + } + @Override public String getWorkspaceId() { return workspaceId; } + public void setWorkspaceId(String workspaceId) { + this.workspaceId = workspaceId; + } + @Override public String getMachineName() { return machineName; } + public void setMachineName(String machineName) { + this.machineName = machineName; + } + @Override public String getEnvName() { return envName; } + public void setEnvName(String envName) { + this.envName = envName; + } + @Override public String getDescription() { return description; } + public void setDescription(String description) { + this.description = description; + } + @Override public boolean isDev() { return this.isDev; } - public void setMachineSource(MachineSource machineSource) { - this.machineSource = machineSource != null ? new MachineSourceImpl(machineSource) : null; - } - - public void setDescription(String description) { - this.description = description; + public void setDev(boolean dev) { + isDev = dev; } @Override @@ -148,7 +221,6 @@ public class SnapshotImpl implements Snapshot { && Objects.equals(id, snapshot.id) && Objects.equals(type, snapshot.type) && Objects.equals(machineSource, snapshot.machineSource) - && Objects.equals(namespace, snapshot.namespace) && Objects.equals(workspaceId, snapshot.workspaceId) && Objects.equals(description, snapshot.description) && Objects.equals(machineName, snapshot.machineName) @@ -163,7 +235,6 @@ public class SnapshotImpl implements Snapshot { hash = hash * 31 + Objects.hashCode(id); hash = hash * 31 + Objects.hashCode(type); hash = hash * 31 + Objects.hashCode(machineSource); - hash = hash * 31 + Objects.hashCode(namespace); hash = hash * 31 + Objects.hashCode(workspaceId); hash = hash * 31 + Objects.hashCode(description); hash = hash * 31 + Objects.hashCode(machineName); @@ -177,7 +248,6 @@ public class SnapshotImpl implements Snapshot { "id='" + id + '\'' + ", type='" + type + '\'' + ", machineSource=" + machineSource + - ", namespace='" + namespace + '\'' + ", creationDate=" + creationDate + ", isDev=" + isDev + ", description='" + description + '\'' + @@ -197,7 +267,6 @@ public class SnapshotImpl implements Snapshot { private String envName; private String id; private String type; - private String namespace; private String description; private MachineSource machineSource; private boolean isDev; @@ -239,11 +308,6 @@ public class SnapshotImpl implements Snapshot { return this; } - public SnapshotBuilder setNamespace(String namespace) { - this.namespace = namespace; - return this; - } - public SnapshotBuilder setDescription(String description) { this.description = description; return this; @@ -270,7 +334,7 @@ public class SnapshotImpl implements Snapshot { } public SnapshotImpl build() { - return new SnapshotImpl(id, type, machineSource, namespace, creationDate, workspaceId, description, isDev, machineName, envName); + return new SnapshotImpl(id, type, machineSource, creationDate, workspaceId, description, isDev, machineName, envName); } } } diff --git a/wsmaster/che-core-api-machine/src/main/java/org/eclipse/che/api/machine/server/recipe/RecipeImpl.java b/wsmaster/che-core-api-machine/src/main/java/org/eclipse/che/api/machine/server/recipe/RecipeImpl.java index 1dafe4d8a7..deb3ccbb3c 100644 --- a/wsmaster/che-core-api-machine/src/main/java/org/eclipse/che/api/machine/server/recipe/RecipeImpl.java +++ b/wsmaster/che-core-api-machine/src/main/java/org/eclipse/che/api/machine/server/recipe/RecipeImpl.java @@ -10,11 +10,18 @@ *******************************************************************************/ package org.eclipse.che.api.machine.server.recipe; -import org.eclipse.che.api.core.acl.AclEntryImpl; import org.eclipse.che.api.core.model.machine.Recipe; +import org.eclipse.che.api.machine.server.jpa.RecipeEntityListener; import org.eclipse.che.api.machine.shared.ManagedRecipe; -import org.eclipse.che.commons.annotation.Nullable; +import javax.persistence.Basic; +import javax.persistence.CollectionTable; +import javax.persistence.Column; +import javax.persistence.ElementCollection; +import javax.persistence.Entity; +import javax.persistence.EntityListeners; +import javax.persistence.Id; +import javax.persistence.Index; import java.util.ArrayList; import java.util.List; import java.util.Objects; @@ -23,17 +30,34 @@ import java.util.Objects; * Implementation of {@link ManagedRecipe} * * @author Eugene Voevodin + * @author Anton Korneta */ +@Entity(name = "Recipe") +@EntityListeners(RecipeEntityListener.class) public class RecipeImpl implements ManagedRecipe { - private String id; - private String name; - private String creator; - private String type; - private String script; - private List tags; - private String description; - private List acl; + @Id + private String id; + + @Basic + private String name; + + @Basic + private String creator; + + @Basic + private String type; + + @Column(columnDefinition = "TEXT") + private String script; + + @Column(columnDefinition = "TEXT") + private String description; + + @ElementCollection + @Column(name = "tag") + @CollectionTable(indexes = @Index(columnList = "tag")) + private List tags; public RecipeImpl() { } @@ -50,8 +74,7 @@ public class RecipeImpl implements ManagedRecipe { recipe.getType(), recipe.getScript(), recipe.getTags(), - recipe.getDescription(), - null); + recipe.getDescription()); } public RecipeImpl(RecipeImpl recipe) { @@ -61,8 +84,7 @@ public class RecipeImpl implements ManagedRecipe { recipe.getType(), recipe.getScript(), recipe.getTags(), - recipe.getDescription(), - recipe.getAcl()); + recipe.getDescription()); } public RecipeImpl(String id, @@ -71,8 +93,7 @@ public class RecipeImpl implements ManagedRecipe { String type, String script, List tags, - String description, - List acl) { + String description) { this.id = id; this.name = name; this.creator = creator; @@ -80,7 +101,6 @@ public class RecipeImpl implements ManagedRecipe { this.script = script; this.tags = tags; this.description = description; - this.acl = acl; } @Override @@ -184,20 +204,6 @@ public class RecipeImpl implements ManagedRecipe { return this; } - @Nullable - public List getAcl() { - return acl; - } - - public void setAcl(List acl) { - this.acl = acl; - } - - public RecipeImpl withAcl(List acl) { - this.acl = acl; - return this; - } - @Override public boolean equals(Object obj) { if (this == obj) { @@ -213,8 +219,7 @@ public class RecipeImpl implements ManagedRecipe { Objects.equals(type, other.type) && Objects.equals(script, other.script) && Objects.equals(description, other.description) && - getTags().equals(other.getTags()) && - Objects.equals(acl, other.acl); + getTags().equals(other.getTags()); } @Override @@ -227,7 +232,6 @@ public class RecipeImpl implements ManagedRecipe { hash = 31 * hash + Objects.hashCode(script); hash = 31 * hash + Objects.hashCode(description); hash = 31 * hash + getTags().hashCode(); - hash = 31 * hash + Objects.hashCode(acl); return hash; } @@ -239,9 +243,8 @@ public class RecipeImpl implements ManagedRecipe { ", creator='" + creator + '\'' + ", type='" + type + '\'' + ", script='" + script + '\'' + - ", tags=" + tags + ", description='" + description + '\'' + - ", acl=" + acl + + ", tags=" + tags + '}'; } } diff --git a/wsmaster/che-core-api-machine/src/main/java/org/eclipse/che/api/machine/server/recipe/RecipeLoader.java b/wsmaster/che-core-api-machine/src/main/java/org/eclipse/che/api/machine/server/recipe/RecipeLoader.java index db97a7cd14..28071fce95 100644 --- a/wsmaster/che-core-api-machine/src/main/java/org/eclipse/che/api/machine/server/recipe/RecipeLoader.java +++ b/wsmaster/che-core-api-machine/src/main/java/org/eclipse/che/api/machine/server/recipe/RecipeLoader.java @@ -20,7 +20,7 @@ import com.google.inject.Inject; import org.eclipse.che.api.core.ConflictException; import org.eclipse.che.api.core.NotFoundException; import org.eclipse.che.api.core.ServerException; -import org.eclipse.che.api.machine.server.dao.RecipeDao; +import org.eclipse.che.api.machine.server.spi.RecipeDao; import org.eclipse.che.commons.annotation.Nullable; import javax.annotation.PostConstruct; @@ -54,7 +54,9 @@ public class RecipeLoader { private final RecipeDao recipeDao; @Inject - public RecipeLoader(@Nullable @Named("predefined.recipe.path") Set recipesPaths, RecipeDao recipeDao) { + @SuppressWarnings("unused") + public RecipeLoader(@Nullable @Named("predefined.recipe.path") Set recipesPaths, + RecipeDao recipeDao) { this.recipesPaths = firstNonNull(recipesPaths, Collections.emptySet()); this.recipeDao = recipeDao; } diff --git a/wsmaster/che-core-api-machine/src/main/java/org/eclipse/che/api/machine/server/recipe/RecipeService.java b/wsmaster/che-core-api-machine/src/main/java/org/eclipse/che/api/machine/server/recipe/RecipeService.java index 2dcae54a50..34c2392449 100644 --- a/wsmaster/che-core-api-machine/src/main/java/org/eclipse/che/api/machine/server/recipe/RecipeService.java +++ b/wsmaster/che-core-api-machine/src/main/java/org/eclipse/che/api/machine/server/recipe/RecipeService.java @@ -16,7 +16,7 @@ import org.eclipse.che.api.core.rest.Service; import org.eclipse.che.api.core.rest.annotations.GenerateLink; import org.eclipse.che.api.core.rest.shared.dto.Link; import org.eclipse.che.api.core.util.LinksHelper; -import org.eclipse.che.api.machine.server.dao.RecipeDao; +import org.eclipse.che.api.machine.server.spi.RecipeDao; import org.eclipse.che.api.machine.shared.ManagedRecipe; import org.eclipse.che.api.machine.shared.dto.recipe.NewRecipe; import org.eclipse.che.api.machine.shared.dto.recipe.RecipeDescriptor; diff --git a/wsmaster/che-core-api-machine/src/main/java/org/eclipse/che/api/machine/server/dao/RecipeDao.java b/wsmaster/che-core-api-machine/src/main/java/org/eclipse/che/api/machine/server/spi/RecipeDao.java similarity index 98% rename from wsmaster/che-core-api-machine/src/main/java/org/eclipse/che/api/machine/server/dao/RecipeDao.java rename to wsmaster/che-core-api-machine/src/main/java/org/eclipse/che/api/machine/server/spi/RecipeDao.java index 0a29561e5f..d7e5edbef8 100644 --- a/wsmaster/che-core-api-machine/src/main/java/org/eclipse/che/api/machine/server/dao/RecipeDao.java +++ b/wsmaster/che-core-api-machine/src/main/java/org/eclipse/che/api/machine/server/spi/RecipeDao.java @@ -8,7 +8,7 @@ * Contributors: * Codenvy, S.A. - initial API and implementation *******************************************************************************/ -package org.eclipse.che.api.machine.server.dao; +package org.eclipse.che.api.machine.server.spi; import org.eclipse.che.api.core.ConflictException; import org.eclipse.che.api.core.NotFoundException; diff --git a/wsmaster/che-core-api-machine/src/main/java/org/eclipse/che/api/machine/server/dao/SnapshotDao.java b/wsmaster/che-core-api-machine/src/main/java/org/eclipse/che/api/machine/server/spi/SnapshotDao.java similarity index 89% rename from wsmaster/che-core-api-machine/src/main/java/org/eclipse/che/api/machine/server/dao/SnapshotDao.java rename to wsmaster/che-core-api-machine/src/main/java/org/eclipse/che/api/machine/server/spi/SnapshotDao.java index 56de18ced8..de8391a576 100644 --- a/wsmaster/che-core-api-machine/src/main/java/org/eclipse/che/api/machine/server/dao/SnapshotDao.java +++ b/wsmaster/che-core-api-machine/src/main/java/org/eclipse/che/api/machine/server/spi/SnapshotDao.java @@ -8,7 +8,7 @@ * Contributors: * Codenvy, S.A. - initial API and implementation *******************************************************************************/ -package org.eclipse.che.api.machine.server.dao; +package org.eclipse.che.api.machine.server.spi; import org.eclipse.che.api.core.NotFoundException; import org.eclipse.che.api.machine.server.exception.SnapshotException; @@ -65,17 +65,15 @@ public interface SnapshotDao { void saveSnapshot(SnapshotImpl snapshot) throws SnapshotException; /** - * Find snapshots by namespaces, workspace, project + * Find snapshots by workspace. * - * @param namespace - * snapshot namespace(e.g. owner). * @param workspaceId - * workspace specified in desired snapshot, optional + * workspace specified in desired snapshot * @return list of snapshot that satisfy provided queries, or empty list if no desired snapshots found * @throws SnapshotException * if error occurs */ - List findSnapshots(String namespace, String workspaceId) throws SnapshotException; + List findSnapshots(String workspaceId) throws SnapshotException; /** * Remove snapshot by id diff --git a/wsmaster/che-core-api-machine/src/test/java/org/eclipse/che/api/machine/server/jpa/JpaTckModule.java b/wsmaster/che-core-api-machine/src/test/java/org/eclipse/che/api/machine/server/jpa/JpaTckModule.java new file mode 100644 index 0000000000..ff7e3c920b --- /dev/null +++ b/wsmaster/che-core-api-machine/src/test/java/org/eclipse/che/api/machine/server/jpa/JpaTckModule.java @@ -0,0 +1,44 @@ +/******************************************************************************* + * Copyright (c) 2012-2016 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 + *******************************************************************************/ +package org.eclipse.che.api.machine.server.jpa; + +import com.google.inject.TypeLiteral; +import com.google.inject.persist.jpa.JpaPersistModule; + +import org.eclipse.che.api.core.jdbc.jpa.eclipselink.EntityListenerInjectionManagerInitializer; +import org.eclipse.che.api.core.jdbc.jpa.guice.JpaInitializer; +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.commons.test.tck.TckModule; +import org.eclipse.che.commons.test.tck.repository.JpaTckRepository; +import org.eclipse.che.commons.test.tck.repository.TckRepository; + +/** + * @author Anton Korneta + */ +public class JpaTckModule extends TckModule { + + @Override + protected void configure() { + install(new JpaPersistModule("main")); + bind(JpaInitializer.class).asEagerSingleton(); + + bind(EntityListenerInjectionManagerInitializer.class).asEagerSingleton(); + + bind(new TypeLiteral>() {}).toInstance(new JpaTckRepository<>(RecipeImpl.class)); + bind(new TypeLiteral>() {}).toInstance(new JpaTckRepository<>(SnapshotImpl.class)); + + bind(RecipeDao.class).to(JpaRecipeDao.class); + bind(SnapshotDao.class).to(JpaSnapshotDao.class); + } +} diff --git a/wsmaster/che-core-api-machine/src/test/java/org/eclipse/che/api/machine/server/recipe/RecipeLoaderTest.java b/wsmaster/che-core-api-machine/src/test/java/org/eclipse/che/api/machine/server/recipe/RecipeLoaderTest.java index d242068224..11c7ce01db 100644 --- a/wsmaster/che-core-api-machine/src/test/java/org/eclipse/che/api/machine/server/recipe/RecipeLoaderTest.java +++ b/wsmaster/che-core-api-machine/src/test/java/org/eclipse/che/api/machine/server/recipe/RecipeLoaderTest.java @@ -13,7 +13,7 @@ package org.eclipse.che.api.machine.server.recipe; import org.eclipse.che.api.core.ConflictException; import org.eclipse.che.api.core.NotFoundException; import org.eclipse.che.api.core.ServerException; -import org.eclipse.che.api.machine.server.dao.RecipeDao; +import org.eclipse.che.api.machine.server.spi.RecipeDao; import org.mockito.Mock; import org.mockito.testng.MockitoTestNGListener; import org.testng.annotations.Listeners; diff --git a/wsmaster/che-core-api-machine/src/test/java/org/eclipse/che/api/machine/server/recipe/RecipeServiceTest.java b/wsmaster/che-core-api-machine/src/test/java/org/eclipse/che/api/machine/server/recipe/RecipeServiceTest.java index f2c755b967..137f42c6c6 100644 --- a/wsmaster/che-core-api-machine/src/test/java/org/eclipse/che/api/machine/server/recipe/RecipeServiceTest.java +++ b/wsmaster/che-core-api-machine/src/test/java/org/eclipse/che/api/machine/server/recipe/RecipeServiceTest.java @@ -15,7 +15,7 @@ import com.jayway.restassured.response.Response; import org.eclipse.che.api.core.rest.ApiExceptionMapper; import org.eclipse.che.api.core.rest.shared.dto.ServiceError; -import org.eclipse.che.api.machine.server.dao.RecipeDao; +import org.eclipse.che.api.machine.server.spi.RecipeDao; import org.eclipse.che.api.machine.shared.dto.recipe.NewRecipe; import org.eclipse.che.api.machine.shared.dto.recipe.RecipeDescriptor; import org.eclipse.che.api.machine.shared.dto.recipe.RecipeUpdate; diff --git a/wsmaster/che-core-api-machine/src/test/java/org/eclipse/che/api/machine/server/spi/tck/RecipeDaoTest.java b/wsmaster/che-core-api-machine/src/test/java/org/eclipse/che/api/machine/server/spi/tck/RecipeDaoTest.java new file mode 100644 index 0000000000..d8c87154d8 --- /dev/null +++ b/wsmaster/che-core-api-machine/src/test/java/org/eclipse/che/api/machine/server/spi/tck/RecipeDaoTest.java @@ -0,0 +1,213 @@ +/******************************************************************************* + * Copyright (c) 2012-2016 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 + *******************************************************************************/ +package org.eclipse.che.api.machine.server.spi.tck; + +import com.google.common.collect.ImmutableList; +import com.google.common.collect.ImmutableSet; + +import org.eclipse.che.api.core.ConflictException; +import org.eclipse.che.api.core.NotFoundException; +import org.eclipse.che.api.machine.server.recipe.RecipeImpl; +import org.eclipse.che.api.machine.server.spi.RecipeDao; +import org.eclipse.che.commons.lang.NameGenerator; +import org.eclipse.che.commons.test.tck.TckModuleFactory; +import org.eclipse.che.commons.test.tck.repository.TckRepository; +import org.testng.annotations.AfterMethod; +import org.testng.annotations.BeforeMethod; +import org.testng.annotations.Guice; +import org.testng.annotations.Test; + +import javax.inject.Inject; +import java.util.ArrayList; +import java.util.HashSet; +import java.util.List; + +import static java.util.Arrays.asList; +import static java.util.Collections.singletonList; +import static org.testng.Assert.assertEquals; +import static org.testng.Assert.assertTrue; + +/** + * Tests {@link RecipeDao} contract. + * + * @author Anton Korneta + */ +@Guice(moduleFactory = TckModuleFactory.class) +@Test(suiteName = RecipeDaoTest.SUITE_NAME) +public class RecipeDaoTest { + + public static final String SUITE_NAME = "RecipeDaoTck"; + + private static final int ENTRY_COUNT = 5; + + private List recipes; + + @Inject + private RecipeDao recipeDao; + + @Inject + private TckRepository tckRepository; + + @BeforeMethod + public void setUp() throws Exception { + recipes = new ArrayList<>(5); + for (int i = 0; i < ENTRY_COUNT; i++) { + recipes.add(createRecipe(i)); + } + tckRepository.createAll(recipes); + } + + @AfterMethod + public void cleanUp() throws Exception { + tckRepository.removeAll(); + } + + @Test(dependsOnMethods = "shouldGetRecipeById") + public void shouldCreateRecipe() throws Exception { + final RecipeImpl recipe = createRecipe(0); + recipeDao.create(recipe); + + assertEquals(recipeDao.getById(recipe.getId()), recipe); + } + + @Test(expectedExceptions = NullPointerException.class) + public void shouldThrowNpeWhenCreateNullRecipe() throws Exception { + recipeDao.create(null); + } + + @Test(expectedExceptions = ConflictException.class) + public void shouldThrowConflictExceptionWhenCreatingRecipeWithExistingId() throws Exception { + recipeDao.create(recipes.get(0)); + } + + @Test + public void shouldUpdateRecipe() throws Exception { + final RecipeImpl update = recipes.get(0).withName("updatedName"); + + assertEquals(recipeDao.update(update), update); + } + + @Test + public void shouldUpdateRecipeWithAllRelatedAttributes() throws Exception { + final RecipeImpl update = recipes.get(0); + update.withName("debian") + .withCreator("userid_9") + .withDescription("description") + .withType("docker") + .setScript("FROM codenvy/debian_jdk8"); + recipeDao.update(update); + + assertEquals(recipeDao.getById(update.getId()), update); + } + + @Test(expectedExceptions = NullPointerException.class) + public void shouldThrowNpeWhenUpdatingRecipeNull() throws Exception { + recipeDao.update(null); + } + + @Test(expectedExceptions = NotFoundException.class) + public void shouldThrowNotFoundExceptionWhenUpdatingNonExistingRecipe() throws Exception { + recipeDao.update(createRecipe(7)); + } + + @Test(expectedExceptions = NotFoundException.class, + dependsOnMethods = "shouldThrowNotFoundExceptionWhenGettingNonExistingRecipe") + public void shouldRemoveRecipe() throws Exception { + final String existedId = recipes.get(0).getId(); + + recipeDao.remove(existedId); + recipeDao.getById(existedId); + } + + @Test(expectedExceptions = NullPointerException.class) + public void shouldThrowNpeWhenRemovingRecipeIdNull() throws Exception { + recipeDao.remove(null); + } + + @Test + public void shouldGetRecipeById() throws Exception { + final RecipeImpl recipe = recipes.get(0); + + assertEquals(recipeDao.getById(recipe.getId()), recipe); + } + + @Test(expectedExceptions = NullPointerException.class) + public void shouldThrowNpeWhenGettingRecipeIdNull() throws Exception { + recipeDao.getById(null); + } + + @Test(expectedExceptions = NotFoundException.class) + public void shouldThrowNotFoundExceptionWhenGettingNonExistingRecipe() throws Exception { + recipeDao.getById("non-existing"); + } + + @Test + public void shouldFindRecipeByUser() throws Exception { + final List result = recipeDao.search(null, + null, + null, + 0, + recipes.size()); + + assertTrue(result.contains(recipes.get(0))); + } + + @Test(dependsOnMethods = "shouldFindRecipeByUser") + public void shouldFindingRecipesByTags() throws Exception { + final List tags = ImmutableList.of("search-by1", "search-by2"); + recipes.get(0).getTags().addAll(tags); + recipes.get(1).getTags().add(tags.get(0)); + recipes.get(2).getTags().add(tags.get(1)); + recipes.get(4).getTags().clear(); + updateAll(); + + final List result = recipeDao.search(null, tags, null, 0, recipes.size()); + + assertEquals(new HashSet<>(result), ImmutableSet.of(recipes.get(0))); + } + + @Test(dependsOnMethods = "shouldFindRecipeByUser") + public void shouldFindRecipeByType() throws Exception { + final RecipeImpl recipe = recipes.get(0); + final List result = recipeDao.search(null, null, recipe.getType(), 0, recipes.size()); + + assertTrue(result.contains(recipe)); + } + + @Test(dependsOnMethods = {"shouldFindRecipeByUser", "shouldFindingRecipesByTags", "shouldFindRecipeByType"}) + public void shouldFindRecipeByUserTagsAndType() throws Exception { + final RecipeImpl recipe = recipes.get(0); + final List result = recipeDao.search(null, + recipe.getTags(), + recipe.getType(), + 0, + 1); + + assertTrue(result.contains(recipe)); + } + + private static RecipeImpl createRecipe(int index) { + final String recipeId = NameGenerator.generate("recipeId", 5); + return new RecipeImpl(recipeId, + "recipeName" + index, + "creator" + index, + "dockerfile" + index, + "script", + new ArrayList<>(asList("tag1" + index, "tag2" + index)), + "recipe description"); + } + + private void updateAll() throws Exception { + for (RecipeImpl recipe : recipes) { + recipeDao.update(recipe); + } + } +} diff --git a/wsmaster/che-core-api-machine/src/test/java/org/eclipse/che/api/machine/server/spi/tck/SnapshotDaoTest.java b/wsmaster/che-core-api-machine/src/test/java/org/eclipse/che/api/machine/server/spi/tck/SnapshotDaoTest.java new file mode 100644 index 0000000000..2b19130516 --- /dev/null +++ b/wsmaster/che-core-api-machine/src/test/java/org/eclipse/che/api/machine/server/spi/tck/SnapshotDaoTest.java @@ -0,0 +1,220 @@ +/******************************************************************************* + * Copyright (c) 2012-2016 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 + *******************************************************************************/ +package org.eclipse.che.api.machine.server.spi.tck; + +import com.google.inject.Inject; + +import org.eclipse.che.api.core.NotFoundException; +import org.eclipse.che.api.machine.server.exception.SnapshotException; +import org.eclipse.che.api.machine.server.model.impl.MachineSourceImpl; +import org.eclipse.che.api.machine.server.model.impl.SnapshotImpl; +import org.eclipse.che.api.machine.server.spi.SnapshotDao; +import org.eclipse.che.commons.test.tck.TckModuleFactory; +import org.eclipse.che.commons.test.tck.repository.TckRepository; +import org.eclipse.che.commons.test.tck.repository.TckRepositoryException; +import org.testng.annotations.AfterMethod; +import org.testng.annotations.BeforeMethod; +import org.testng.annotations.DataProvider; +import org.testng.annotations.Guice; +import org.testng.annotations.Test; + +import java.util.HashSet; +import java.util.List; + +import static java.util.Arrays.asList; +import static org.testng.Assert.assertEquals; +import static org.testng.Assert.fail; + +/** + * Tests {@link SnapshotDao} contract. + * + * @author Yevhenii Voevodin + */ +@Guice(moduleFactory = TckModuleFactory.class) +@Test(suiteName = SnapshotDaoTest.SUITE_NAME) +public class SnapshotDaoTest { + + public static final String SUITE_NAME = "SnapshotDaoTest"; + + private static final int SNAPSHOTS_SIZE = 6; + + private SnapshotImpl[] snapshots; + + @Inject + private SnapshotDao snapshotDao; + + @Inject + private TckRepository snaphotRepo; + + @BeforeMethod + private void createSnapshots() throws TckRepositoryException { + snapshots = new SnapshotImpl[SNAPSHOTS_SIZE]; + for (int i = 0; i < SNAPSHOTS_SIZE; i++) { + snapshots[i] = createSnapshot("snapshot-" + i, + "workspace-" + i / 3, // 3 snapshot share the same workspace id + "environment-" + i / 2, // 2 snapshots share the same env name + "machine-" + i); + } + snaphotRepo.createAll(asList(snapshots)); + } + + @AfterMethod + private void removeSnapshots() throws TckRepositoryException { + snaphotRepo.removeAll(); + } + + @Test + public void shouldGetSnapshotById() throws Exception { + final SnapshotImpl snapshot = snapshots[0]; + + assertEquals(snapshotDao.getSnapshot(snapshot.getId()), snapshot); + } + + @Test(expectedExceptions = NotFoundException.class) + public void shouldThrowNotFoundExceptionWhenGettingNonExistingSnapshot() throws Exception { + snapshotDao.getSnapshot("non-existing-snapshot"); + } + + @Test(expectedExceptions = NullPointerException.class) + public void shouldThrowNpeWhenGettingSnapshotByNullId() throws Exception { + snapshotDao.getSnapshot(null); + } + + @Test + public void shouldGetSnapshotByWorkspaceEnvironmentAndMachineName() throws Exception { + final SnapshotImpl snapshot = snapshots[0]; + + assertEquals(snapshotDao.getSnapshot(snapshot.getWorkspaceId(), + snapshot.getEnvName(), + snapshot.getMachineName()), snapshot); + } + + @Test(expectedExceptions = NotFoundException.class, dataProvider = "missingSnapshots") + public void shouldThrowNotFoundExceptionWhenSnapshotMissing(String wsId, String envName, String machineName) throws Exception { + snapshotDao.getSnapshot(wsId, envName, machineName); + } + + @Test(expectedExceptions = NullPointerException.class, dataProvider = "nullParameterVariations") + public void shouldThrowNpeWhenAnyOfGetSnapshotParametersIsNull(String wsId, String envName, String machineName) throws Exception { + snapshotDao.getSnapshot(wsId, envName, machineName); + } + + @Test + public void shouldFindSnapshotsByWorkspaceAndNamespace() throws Exception { + final SnapshotImpl snapshot = snapshots[0]; + + final List found = snapshotDao.findSnapshots(snapshot.getWorkspaceId()); + + assertEquals(new HashSet<>(found), new HashSet<>(asList(snapshots[0], snapshots[1], snapshots[2]))); + } + + @Test(expectedExceptions = NullPointerException.class) + public void shouldThrowNpeWhenSearchingSnapshotsByNullWorkspaceId() throws Exception { + snapshotDao.findSnapshots(null); + } + + @Test(dependsOnMethods = "shouldGetSnapshotById") + public void shouldSaveSnapshot() throws Exception { + final SnapshotImpl newSnapshot = createSnapshot("new-snapshot", + "workspace-id", + "env-name", + "machine-name"); + + snapshotDao.saveSnapshot(newSnapshot); + + assertEquals(snapshotDao.getSnapshot(newSnapshot.getId()), new SnapshotImpl(newSnapshot)); + } + + @Test(expectedExceptions = SnapshotException.class) + public void shouldNotSaveSnapshotWithReservedId() throws Exception { + final SnapshotImpl snapshot = snapshots[0]; + snapshot.setWorkspaceId("new-workspace"); + snapshot.setEnvName("new-env"); + snapshot.setMachineName("new-machine"); + + snapshotDao.saveSnapshot(snapshot); + } + + @Test(expectedExceptions = SnapshotException.class) + public void shouldNotSaveSnapshotForMachineIfSnapshotForSuchMachineAlreadyExists() throws Exception { + final SnapshotImpl snapshot = snapshots[0]; + snapshot.setId("new-id"); + + snapshotDao.saveSnapshot(snapshot); + } + + @Test(expectedExceptions = NullPointerException.class) + public void shouldThrowNpeWhenSavingNull() throws Exception { + snapshotDao.saveSnapshot(null); + } + + @Test(expectedExceptions = NotFoundException.class, + dependsOnMethods = "shouldThrowNotFoundExceptionWhenGettingNonExistingSnapshot") + public void shouldRemoveSnapshot() throws Exception { + final SnapshotImpl snapshot = snapshots[0]; + + try { + snapshotDao.removeSnapshot(snapshot.getId()); + } catch (NotFoundException x) { + fail("Should remove snapshot"); + } + + snapshotDao.getSnapshot(snapshot.getId()); + } + + @Test(expectedExceptions = NotFoundException.class) + public void shouldThrowNotFoundExceptionWhenRemovingNonExistingSnapshot() throws Exception { + snapshotDao.removeSnapshot("non-existing-id"); + } + + @Test(expectedExceptions = NullPointerException.class) + public void shouldThrowNpeWhenRemovingNull() throws Exception { + snapshotDao.removeSnapshot(null); + } + + @DataProvider(name = "missingSnapshots") + public Object[][] missingSnapshots() { + final SnapshotImpl snapshot = snapshots[0]; + return new Object[][] { + {"non-existing-workspace-id", snapshot.getEnvName(), snapshot.getMachineName()}, + {snapshot.getWorkspaceId(), "non-existing-env", snapshot.getMachineName()}, + {snapshot.getWorkspaceId(), snapshot.getEnvName(), "non-existing-machine-name"} + }; + } + + @DataProvider(name = "nullParameterVariations") + public Object[][] nullParameterVariations() { + final SnapshotImpl snapshot = snapshots[0]; + return new Object[][] { + {null, snapshot.getEnvName(), snapshot.getMachineName()}, + {snapshot.getWorkspaceId(), null, snapshot.getMachineName()}, + {snapshot.getWorkspaceId(), snapshot.getEnvName(), null} + }; + } + + private static SnapshotImpl createSnapshot(String id, + String workspaceId, + String envName, + String machineName) { + return SnapshotImpl.builder() + .setId(id) + .setType(id + "type") + .setMachineSource(new MachineSourceImpl(id + "source-type", + id + "source-location", + id + "source-content")) + .setCreationDate(System.currentTimeMillis()) + .setDev(true) + .setWorkspaceId(workspaceId) + .setEnvName(envName) + .setMachineName(machineName) + .build(); + } +} diff --git a/wsmaster/che-core-api-machine/src/test/resources/META-INF/persistence.xml b/wsmaster/che-core-api-machine/src/test/resources/META-INF/persistence.xml new file mode 100644 index 0000000000..6b7e9b56ef --- /dev/null +++ b/wsmaster/che-core-api-machine/src/test/resources/META-INF/persistence.xml @@ -0,0 +1,37 @@ + + + + org.eclipse.persistence.jpa.PersistenceProvider + org.eclipse.che.api.machine.server.recipe.RecipeImpl + org.eclipse.che.api.machine.server.model.impl.MachineSourceImpl + org.eclipse.che.api.machine.server.model.impl.SnapshotImpl + true + + + + + + + + + + + + + + + + diff --git a/wsmaster/che-core-api-machine/src/test/resources/META-INF/services/org.eclipse.che.commons.test.tck.TckModule b/wsmaster/che-core-api-machine/src/test/resources/META-INF/services/org.eclipse.che.commons.test.tck.TckModule new file mode 100644 index 0000000000..aac7532421 --- /dev/null +++ b/wsmaster/che-core-api-machine/src/test/resources/META-INF/services/org.eclipse.che.commons.test.tck.TckModule @@ -0,0 +1 @@ +org.eclipse.che.api.machine.server.jpa.JpaTckModule diff --git a/wsmaster/che-core-api-ssh/pom.xml b/wsmaster/che-core-api-ssh/pom.xml index dcbea4f417..bb38df2fbc 100644 --- a/wsmaster/che-core-api-ssh/pom.xml +++ b/wsmaster/che-core-api-ssh/pom.xml @@ -34,6 +34,14 @@ com.google.guava guava + + com.google.inject + guice + + + com.google.inject.extensions + guice-persist + com.jcraft jsch @@ -46,6 +54,10 @@ io.swagger swagger-annotations + + javax.annotation + javax.annotation-api + javax.inject javax.inject @@ -62,14 +74,38 @@ org.eclipse.che.core che-core-api-dto + + org.eclipse.che.core + che-core-api-jdbc + org.eclipse.che.core che-core-api-ssh-shared + + org.eclipse.che.core + che-core-api-user + org.eclipse.che.core che-core-commons-annotations + + org.eclipse.che.core + che-core-commons-lang + + + org.eclipse.che.core + che-core-commons-test + + + org.eclipse.persistence + javax.persistence + + + org.slf4j + slf4j-api + com.google.gwt gwt-dev @@ -80,6 +116,21 @@ gwtmockito test + + com.h2database + h2 + test + + + org.eclipse.che.core + che-core-api-jdbc-vendor-h2 + test + + + org.eclipse.persistence + eclipselink + test + org.everrest everrest-assured @@ -196,6 +247,23 @@ + + + org.apache.maven.plugins + maven-jar-plugin + + + + test-jar + + + + **/spi/tck/*.* + + + + + diff --git a/wsmaster/che-core-api-ssh/src/main/java/org/eclipse/che/api/ssh/server/SshManager.java b/wsmaster/che-core-api-ssh/src/main/java/org/eclipse/che/api/ssh/server/SshManager.java index eb1fcf01d6..6cac1d6ba7 100644 --- a/wsmaster/che-core-api-ssh/src/main/java/org/eclipse/che/api/ssh/server/SshManager.java +++ b/wsmaster/che-core-api-ssh/src/main/java/org/eclipse/che/api/ssh/server/SshManager.java @@ -70,19 +70,18 @@ public class SshManager { ByteArrayOutputStream publicBuff = new ByteArrayOutputStream(); keyPair.writePublicKey(publicBuff, null); - final SshPairImpl generatedSshPair = new SshPairImpl(service, + final SshPairImpl generatedSshPair = new SshPairImpl(owner, + service, name, publicBuff.toString(), privateBuff.toString()); - sshDao.create(owner, generatedSshPair); + sshDao.create(generatedSshPair); return generatedSshPair; } /** * Creates new ssh pair for specified user. * - * @param owner - * the id of the user who will be the owner of the ssh pair * @param sshPair * ssh pair to create * @throws ConflictException @@ -90,8 +89,8 @@ public class SshManager { * @throws ServerException * when any other error occurs during ssh pair creating */ - public void createPair(String owner, SshPairImpl sshPair) throws ServerException, ConflictException { - sshDao.create(owner, sshPair); + public void createPair(SshPairImpl sshPair) throws ServerException, ConflictException { + sshDao.create(sshPair); } /** diff --git a/wsmaster/che-core-api-ssh/src/main/java/org/eclipse/che/api/ssh/server/SshService.java b/wsmaster/che-core-api-ssh/src/main/java/org/eclipse/che/api/ssh/server/SshService.java index 67a2008d5c..cc85e59283 100644 --- a/wsmaster/che-core-api-ssh/src/main/java/org/eclipse/che/api/ssh/server/SshService.java +++ b/wsmaster/che-core-api-ssh/src/main/java/org/eclipse/che/api/ssh/server/SshService.java @@ -131,7 +131,7 @@ public class SshService extends Service { throw new BadRequestException("Key content was not provided."); } - sshManager.createPair(getCurrentUserId(), new SshPairImpl(service, name, publicKey, privateKey)); + sshManager.createPair(new SshPairImpl(getCurrentUserId(), service, name, publicKey, privateKey)); // We should send 200 response code and body with empty line // through specific of html form that doesn't invoke complete submit handler @@ -158,7 +158,7 @@ public class SshService extends Service { throw new BadRequestException("Key content was not provided."); } - sshManager.createPair(getCurrentUserId(), new SshPairImpl(sshPair)); + sshManager.createPair(new SshPairImpl(getCurrentUserId(), sshPair)); } @GET diff --git a/wsmaster/che-core-api-ssh/src/main/java/org/eclipse/che/api/ssh/server/jpa/JpaSshDao.java b/wsmaster/che-core-api-ssh/src/main/java/org/eclipse/che/api/ssh/server/jpa/JpaSshDao.java new file mode 100644 index 0000000000..88ae25370d --- /dev/null +++ b/wsmaster/che-core-api-ssh/src/main/java/org/eclipse/che/api/ssh/server/jpa/JpaSshDao.java @@ -0,0 +1,168 @@ +/******************************************************************************* + * Copyright (c) 2012-2016 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 + *******************************************************************************/ +package org.eclipse.che.api.ssh.server.jpa; + +import com.google.inject.persist.Transactional; + +import org.eclipse.che.api.core.ConflictException; +import org.eclipse.che.api.core.NotFoundException; +import org.eclipse.che.api.core.ServerException; +import org.eclipse.che.api.core.jdbc.jpa.DuplicateKeyException; +import org.eclipse.che.api.core.notification.EventService; +import org.eclipse.che.api.core.notification.EventSubscriber; +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.event.BeforeUserRemovedEvent; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import javax.annotation.PostConstruct; +import javax.annotation.PreDestroy; +import javax.inject.Inject; +import javax.inject.Provider; +import javax.inject.Singleton; +import javax.persistence.EntityManager; +import java.util.List; + +import static java.lang.String.format; +import static java.util.Objects.requireNonNull; + +/** + * JPA based implementation of {@link SshDao}. + * + * @author Mihail Kuznyetsov + * @author Yevhenii Voevodin + */ +@Singleton +public class JpaSshDao implements SshDao { + + private static final Logger LOG = LoggerFactory.getLogger(JpaSshDao.class); + + @Inject + private Provider managerProvider; + + @Override + public void create(SshPairImpl sshPair) throws ServerException, ConflictException { + requireNonNull(sshPair); + try { + doCreate(sshPair); + } catch (DuplicateKeyException e) { + throw new ConflictException(format("Ssh pair with service '%s' and name '%s' already exists", + sshPair.getService(), + sshPair.getName())); + } catch (RuntimeException e) { + throw new ServerException(e); + } + } + + @Override + @Transactional + public List get(String owner, String service) throws ServerException { + requireNonNull(owner); + requireNonNull(service); + try { + return managerProvider.get() + .createNamedQuery("SshKeyPair.getByOwnerAndService", SshPairImpl.class) + .setParameter("owner", owner) + .setParameter("service", service) + .getResultList(); + } catch (RuntimeException e) { + throw new ServerException(e.getLocalizedMessage(), e); + } + } + + @Override + @Transactional + public SshPairImpl get(String owner, String service, String name) throws ServerException, NotFoundException { + requireNonNull(owner); + requireNonNull(service); + requireNonNull(name); + try { + SshPairImpl result = managerProvider.get().find(SshPairImpl.class, new SshPairPrimaryKey(owner, service, name)); + if (result == null) { + throw new NotFoundException(format("Ssh pair with service '%s' and name '%s' was not found.", service, name)); + } + return result; + } catch (RuntimeException e) { + throw new ServerException(e.getLocalizedMessage(), e); + } + } + + @Override + public void remove(String owner, String service, String name) throws ServerException, NotFoundException { + requireNonNull(owner); + requireNonNull(service); + requireNonNull(name); + try { + doRemove(owner, service, name); + } catch (RuntimeException e) { + throw new ServerException(e); + } + } + + @Override + @Transactional + public List get(String owner) throws ServerException { + requireNonNull(owner, "Required non-null owner"); + try { + return managerProvider.get() + .createNamedQuery("SshKeyPair.getByOwner", SshPairImpl.class) + .setParameter("owner", owner) + .getResultList(); + } catch (RuntimeException x) { + throw new ServerException(x.getLocalizedMessage(), x); + } + } + + @Transactional + protected void doCreate(SshPairImpl entity) { + managerProvider.get().persist(entity); + } + + @Transactional + protected void doRemove(String owner, String service, String name) throws NotFoundException { + EntityManager manager = managerProvider.get(); + SshPairImpl entity = manager.find(SshPairImpl.class, new SshPairPrimaryKey(owner, service, name)); + if (entity == null) { + throw new NotFoundException(format("Ssh pair with service '%s' and name '%s' was not found.", service, name)); + } + manager.remove(entity); + } + + @Singleton + public static class RemoveSshKeysBeforeUserRemovedEventSubscriber implements EventSubscriber { + @Inject + private SshDao sshDao; + @Inject + private EventService eventService; + + @PostConstruct + public void subscribe() { + eventService.subscribe(this); + } + + @PreDestroy + public void unsubscribe() { + eventService.unsubscribe(this); + } + + @Override + public void onEvent(BeforeUserRemovedEvent event) { + try { + for (SshPairImpl sshPair : sshDao.get(event.getUser().getId())) { + sshDao.remove(sshPair.getOwner(), sshPair.getService(), sshPair.getName()); + } + } catch (Exception x) { + LOG.error(format("Couldn't remove ssh keys before user '%s' is removed", event.getUser().getId()), x); + } + } + } +} diff --git a/wsmaster/che-core-api-ssh/src/main/java/org/eclipse/che/api/ssh/server/jpa/SshJpaModule.java b/wsmaster/che-core-api-ssh/src/main/java/org/eclipse/che/api/ssh/server/jpa/SshJpaModule.java new file mode 100644 index 0000000000..9686e46e49 --- /dev/null +++ b/wsmaster/che-core-api-ssh/src/main/java/org/eclipse/che/api/ssh/server/jpa/SshJpaModule.java @@ -0,0 +1,28 @@ +/******************************************************************************* + * Copyright (c) 2012-2016 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 + *******************************************************************************/ +package org.eclipse.che.api.ssh.server.jpa; + +import com.google.inject.AbstractModule; + +import org.eclipse.che.api.ssh.server.jpa.JpaSshDao.RemoveSshKeysBeforeUserRemovedEventSubscriber; +import org.eclipse.che.api.ssh.server.spi.SshDao; + +/** + * @author Yevhenii Voevodin + */ +public class SshJpaModule extends AbstractModule { + + @Override + protected void configure() { + bind(SshDao.class).to(JpaSshDao.class); + bind(RemoveSshKeysBeforeUserRemovedEventSubscriber.class).asEagerSingleton(); + } +} diff --git a/wsmaster/che-core-api-ssh/src/main/java/org/eclipse/che/api/ssh/server/jpa/SshPairPrimaryKey.java b/wsmaster/che-core-api-ssh/src/main/java/org/eclipse/che/api/ssh/server/jpa/SshPairPrimaryKey.java new file mode 100644 index 0000000000..aa50eb8786 --- /dev/null +++ b/wsmaster/che-core-api-ssh/src/main/java/org/eclipse/che/api/ssh/server/jpa/SshPairPrimaryKey.java @@ -0,0 +1,67 @@ +/******************************************************************************* + * Copyright (c) 2012-2016 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 + *******************************************************************************/ +package org.eclipse.che.api.ssh.server.jpa; + +import org.eclipse.che.api.ssh.server.model.impl.SshPairImpl; + +import java.io.Serializable; +import java.util.Objects; + +/** + * Primary key for {@link SshPairImpl} entity + * + * @author Mihail Kuznyetsov + */ +public class SshPairPrimaryKey implements Serializable { + private String owner; + private String service; + private String name; + + public SshPairPrimaryKey() { + } + + public SshPairPrimaryKey(String owner, String service, String name) { + this.owner = owner; + this.service = service; + this.name = name; + } + + public String getOwner() { + return owner; + } + + public String getService() { + return service; + } + + public String getName() { + return name; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) return true; + if (!(obj instanceof SshPairPrimaryKey)) return false; + final SshPairPrimaryKey other = (SshPairPrimaryKey)obj; + return Objects.equals(owner, other.owner) && + Objects.equals(service, other.service) && + Objects.equals(name, other.name); + } + + @Override + public int hashCode() { + int hash = 7; + hash = 31 * hash + Objects.hashCode(owner); + hash = 31 * hash + Objects.hashCode(service); + hash = 31 * hash + Objects.hashCode(name); + return hash; + } +} diff --git a/wsmaster/che-core-api-ssh/src/main/java/org/eclipse/che/api/ssh/server/model/impl/SshPairImpl.java b/wsmaster/che-core-api-ssh/src/main/java/org/eclipse/che/api/ssh/server/model/impl/SshPairImpl.java index ecda79344e..c9734ca600 100644 --- a/wsmaster/che-core-api-ssh/src/main/java/org/eclipse/che/api/ssh/server/model/impl/SshPairImpl.java +++ b/wsmaster/che-core-api-ssh/src/main/java/org/eclipse/che/api/ssh/server/model/impl/SshPairImpl.java @@ -10,34 +10,86 @@ *******************************************************************************/ package org.eclipse.che.api.ssh.server.model.impl; +import org.eclipse.che.api.ssh.server.jpa.SshPairPrimaryKey; import org.eclipse.che.api.ssh.shared.model.SshPair; +import org.eclipse.che.api.user.server.model.impl.UserImpl; import org.eclipse.che.commons.annotation.Nullable; +import javax.persistence.Column; +import javax.persistence.Entity; +import javax.persistence.Id; +import javax.persistence.IdClass; +import javax.persistence.JoinColumn; +import javax.persistence.ManyToOne; +import javax.persistence.NamedQueries; +import javax.persistence.NamedQuery; import java.util.Objects; /** * @author Sergii Leschenko */ +@Entity(name = "SshKeyPair") +@NamedQueries( + { + @NamedQuery(name = "SshKeyPair.getByOwnerAndService", + query = "SELECT pair " + + "FROM SshKeyPair pair " + + "WHERE pair.owner = :owner " + + " AND pair.service = :service"), + @NamedQuery(name = "SshKeyPair.getByOwner", + query = "SELECT pair " + + "FROM SshKeyPair pair " + + "WHERE pair.owner = :owner") + } +) +@IdClass(SshPairPrimaryKey.class) public class SshPairImpl implements SshPair { - private final String service; - private final String name; - private final String publicKey; - private final String privateKey; + @Id + private String owner; - public SshPairImpl(String service, String name, String publicKey, String privateKey) { + @Id + private String service; + + @Id + private String name; + + @Column(columnDefinition = "TEXT") + private String publicKey; + + @Column(columnDefinition = "TEXT") + private String privateKey; + + @ManyToOne + @JoinColumn(name = "owner", insertable = false, updatable = false) + private UserImpl user; + + public SshPairImpl() { + } + + public SshPairImpl(String owner, String service, String name, String publicKey, String privateKey) { + this.owner = owner; this.service = service; this.name = name; this.publicKey = publicKey; this.privateKey = privateKey; } - public SshPairImpl(SshPair sshPair) { + public SshPairImpl(String owner, SshPair sshPair) { + this.owner = owner; this.service = sshPair.getService(); this.name = sshPair.getName(); this.publicKey = sshPair.getPublicKey(); this.privateKey = sshPair.getPrivateKey(); } + public SshPairImpl(SshPairImpl sshPair) { + this(sshPair.owner, sshPair.service, sshPair.name, sshPair.publicKey, sshPair.privateKey); + } + + public String getOwner() { + return owner; + } + @Override public String getService() { return service; @@ -65,7 +117,8 @@ public class SshPairImpl implements SshPair { if (this == obj) return true; if (!(obj instanceof SshPairImpl)) return false; final SshPairImpl other = (SshPairImpl)obj; - return Objects.equals(service, other.service) && + return Objects.equals(owner, other.owner) && + Objects.equals(service, other.service) && Objects.equals(name, other.name) && Objects.equals(publicKey, other.publicKey) && Objects.equals(privateKey, other.privateKey); @@ -74,6 +127,7 @@ public class SshPairImpl implements SshPair { @Override public int hashCode() { int hash = 7; + hash = 31 * hash + Objects.hashCode(owner); hash = 31 * hash + Objects.hashCode(service); hash = 31 * hash + Objects.hashCode(name); hash = 31 * hash + Objects.hashCode(publicKey); @@ -84,7 +138,8 @@ public class SshPairImpl implements SshPair { @Override public String toString() { return "SshPairImpl{" + - "service='" + service + '\'' + + "owner='" + owner + '\'' + + ", service='" + service + '\'' + ", name='" + name + '\'' + ", publicKey='" + publicKey + '\'' + ", privateKey='" + privateKey + '\'' + diff --git a/wsmaster/che-core-api-ssh/src/main/java/org/eclipse/che/api/ssh/server/spi/SshDao.java b/wsmaster/che-core-api-ssh/src/main/java/org/eclipse/che/api/ssh/server/spi/SshDao.java index c65e4ca4c8..88f030f755 100644 --- a/wsmaster/che-core-api-ssh/src/main/java/org/eclipse/che/api/ssh/server/spi/SshDao.java +++ b/wsmaster/che-core-api-ssh/src/main/java/org/eclipse/che/api/ssh/server/spi/SshDao.java @@ -28,16 +28,16 @@ public interface SshDao { /** * Creates new ssh pair for specified user. * - * @param owner - * the id of the user who will be the owner of the ssh pair * @param sshPair * ssh pair to create * @throws ConflictException * when specified user already has ssh pair with given service and name + * @throws NullPointerException + * when {@code sshPair} is null * @throws ServerException * when any other error occurs during ssh pair creating */ - void create(String owner, SshPairImpl sshPair) throws ServerException, ConflictException; + void create(SshPairImpl sshPair) throws ServerException, ConflictException; /** * Returns ssh pairs by owner and service. @@ -47,6 +47,8 @@ public interface SshDao { * @param service * service name of ssh pair * @return list of ssh pair with given service and owned by given service. + * @throws NullPointerException + * when {@code owner} or {@code service} is null * @throws ServerException * when any other error occurs during ssh pair fetching */ @@ -62,6 +64,8 @@ public interface SshDao { * @param name * name of ssh pair * @return ssh pair instance + * @throws NullPointerException + * when {@code owner} or {@code service} or {@code name} is null * @throws NotFoundException * when ssh pair is not found * @throws ServerException @@ -78,10 +82,26 @@ public interface SshDao { * service name of ssh pair * @param name * of ssh pair + * @throws NullPointerException + * when {@code owner} or {@code service} or {@code name} is null * @throws NotFoundException * when ssh pair is not found * @throws ServerException * when any other error occurs during ssh pair removing */ void remove(String owner, String service, String name) throws ServerException, NotFoundException; + + /** + * Gets ssh pairs by owner. + * + * @param owner + * the owner of the ssh key + * @return the list of the ssh key pairs owned by the {@code owner}, or empty list if + * there are no ssh key pairs by the given {@code owner} + * @throws NullPointerException + * when {@code owner} is null + * @throws ServerException + * when any error occurs(e.g. database connection error) + */ + List get(String owner) throws ServerException; } diff --git a/wsmaster/che-core-api-ssh/src/test/java/org/eclipse/che/api/ssh/server/jpa/SshTckModule.java b/wsmaster/che-core-api-ssh/src/test/java/org/eclipse/che/api/ssh/server/jpa/SshTckModule.java new file mode 100644 index 0000000000..e70495dd86 --- /dev/null +++ b/wsmaster/che-core-api-ssh/src/test/java/org/eclipse/che/api/ssh/server/jpa/SshTckModule.java @@ -0,0 +1,41 @@ +/******************************************************************************* + * Copyright (c) 2012-2016 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 + *******************************************************************************/ +package org.eclipse.che.api.ssh.server.jpa; + +import com.google.inject.TypeLiteral; +import com.google.inject.persist.jpa.JpaPersistModule; + +import org.eclipse.che.api.core.jdbc.jpa.eclipselink.EntityListenerInjectionManagerInitializer; +import org.eclipse.che.api.core.jdbc.jpa.guice.JpaInitializer; +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.model.impl.UserImpl; +import org.eclipse.che.commons.test.tck.TckModule; +import org.eclipse.che.commons.test.tck.repository.JpaTckRepository; +import org.eclipse.che.commons.test.tck.repository.TckRepository; + +/** + * @author Mihail Kuznyetsov + */ +public class SshTckModule extends TckModule { + + @Override + protected void configure() { + bind(SshDao.class).to(JpaSshDao.class); + bind(new TypeLiteral>(){}).toInstance(new JpaTckRepository<>(SshPairImpl.class)); + bind(new TypeLiteral>(){}).toInstance(new JpaTckRepository<>(UserImpl.class)); + + install(new JpaPersistModule("main")); + bind(JpaInitializer.class).asEagerSingleton(); + bind(EntityListenerInjectionManagerInitializer.class).asEagerSingleton(); + bind(org.eclipse.che.api.core.h2.jdbc.jpa.eclipselink.H2ExceptionHandler.class); + } +} diff --git a/wsmaster/che-core-api-ssh/src/test/java/org/eclipse/che/api/ssh/server/spi/tck/SshDaoTest.java b/wsmaster/che-core-api-ssh/src/test/java/org/eclipse/che/api/ssh/server/spi/tck/SshDaoTest.java new file mode 100644 index 0000000000..ab046262c0 --- /dev/null +++ b/wsmaster/che-core-api-ssh/src/test/java/org/eclipse/che/api/ssh/server/spi/tck/SshDaoTest.java @@ -0,0 +1,210 @@ +/******************************************************************************* + * Copyright (c) 2012-2016 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 + *******************************************************************************/ +package org.eclipse.che.api.ssh.server.spi.tck; + +import org.eclipse.che.api.core.ConflictException; +import org.eclipse.che.api.core.NotFoundException; +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.model.impl.UserImpl; +import org.eclipse.che.commons.lang.NameGenerator; +import org.eclipse.che.commons.test.tck.TckModuleFactory; +import org.eclipse.che.commons.test.tck.repository.TckRepository; +import org.eclipse.che.commons.test.tck.repository.TckRepositoryException; +import org.testng.annotations.AfterMethod; +import org.testng.annotations.BeforeMethod; +import org.testng.annotations.Guice; +import org.testng.annotations.Test; + +import javax.inject.Inject; +import java.util.Arrays; +import java.util.HashSet; +import java.util.List; + +import static java.util.Arrays.asList; +import static java.util.Collections.emptyList; +import static org.testng.Assert.assertEquals; +import static org.testng.Assert.assertTrue; +import static org.testng.Assert.fail; + +/** + * Tests {@link SshDao} interface contract. + * + * @author Mihail Kuznyetsov. + * @author Yevhenii Voevodin + */ +@Guice(moduleFactory = TckModuleFactory.class) +@Test(suiteName = SshDaoTest.SUITE_NAME) +public class SshDaoTest { + public static final String SUITE_NAME = "SshDaoTck"; + private static final int COUNT_OF_PAIRS = 6; + private static final int COUNT_OF_USERS = 3; + + SshPairImpl[] pairs; + + @Inject + private SshDao sshDao; + + @Inject + private TckRepository sshRepository; + + @Inject + private TckRepository userRepository; + + @BeforeMethod + public void setUp() throws TckRepositoryException { + UserImpl[] users = new UserImpl[COUNT_OF_USERS]; + for (int i = 0; i < COUNT_OF_USERS; i++) { + users[i] = new UserImpl("owner" + i, + "owner" + i + "@eclipse.org", + "owner" + i, + "password", + emptyList()); + } + + pairs = new SshPairImpl[COUNT_OF_PAIRS]; + + for (int i = 0; i < COUNT_OF_PAIRS; i++) { + pairs[i] = new SshPairImpl("owner" + i/3, // 3 each pairs share the same owner + "service" + i/2, // each 2 pairs share the same service + "name" + i, + NameGenerator.generate("publicKey-", 20), + NameGenerator.generate("privateKey-", 20)); + } + + userRepository.createAll(Arrays.asList(users)); + sshRepository.createAll(Arrays.asList(pairs)); + } + + @AfterMethod + public void cleanUp() throws TckRepositoryException { + sshRepository.removeAll(); + userRepository.removeAll(); + } + + @Test(dependsOnMethods = "shouldGetSshPairByNameOwnerAndService") + public void shouldCreateSshKeyPair() throws Exception { + SshPairImpl pair = new SshPairImpl("owner1", "service", "name", "publicKey", "privateKey"); + sshDao.create(pair); + + assertEquals(sshDao.get("owner1", "service", "name"), pair); + } + + @Test(expectedExceptions = ConflictException.class) + public void shouldThrowConflictExceptionWhenSshPairWithSuchOwnerAndServiceAndNameAlreadyExists() throws Exception { + sshDao.create(pairs[0]); + } + + @Test(expectedExceptions = NullPointerException.class) + public void shouldThrowNpeOnCreateIfSshPairIsNull() throws Exception { + sshDao.create(null); + } + @Test + public void shouldGetSshPairByNameOwnerAndService() throws Exception{ + SshPairImpl sshPair = pairs[0]; + + sshDao.get(sshPair.getOwner(), sshPair.getService(), sshPair.getName()); + } + + @Test(expectedExceptions = NotFoundException.class) + public void shouldThrowNotFoundExceptionIfPairWithSuchNameOwnerAndServiceDoesNotExist() throws Exception { + SshPairImpl sshPair = pairs[0]; + + sshDao.get(sshPair.getService(), sshPair.getService(), sshPair.getName()); + } + + @Test(expectedExceptions = NullPointerException.class) + public void shouldThrowNpeOnGetSshPairWhenOwnerIsNull() throws Exception{ + sshDao.get(null, "service", "name"); + } + + @Test(expectedExceptions = NullPointerException.class) + public void shouldThrowNpeOnGetSshPairWhenServiceIsNull() throws Exception{ + sshDao.get("owner", null, "name"); + } + + @Test(expectedExceptions = NullPointerException.class) + public void shouldThrowNpeOnGetSshPairWhenNameIsNull() throws Exception{ + sshDao.get("owner", "service", null); + } + + @Test + public void shouldGetSshPairListByNameAndService() throws Exception{ + SshPairImpl sshPair1 = pairs[0]; + SshPairImpl sshPair2 = pairs[1]; + assertEquals(sshPair1.getOwner(), sshPair2.getOwner(), "Owner must be the same"); + assertEquals(sshPair1.getService(), sshPair2.getService(), "Service must be the same"); + + final List found = sshDao.get(sshPair1.getOwner(), sshPair1.getService()); + assertEquals(new HashSet<>(found), new HashSet<>(asList(sshPair1, sshPair2))); + } + + @Test + public void shouldReturnEmptyListWhenThereAreNoPairsWithGivenOwnerAndService() throws Exception { + assertTrue(sshDao.get("non-existing-owner", "non-existing-service").isEmpty()); + } + + @Test(expectedExceptions = NullPointerException.class) + public void shouldThrowNpeOnGetSshPairsListWhenOwnerIsNull() throws Exception{ + sshDao.get(null, "service"); + } + + @Test(expectedExceptions = NullPointerException.class) + public void shouldThrowNpeOnGetSshPairsListWhenServiceIsNull() throws Exception{ + sshDao.get("owner", null); + } + + @Test(expectedExceptions = NotFoundException.class, + dependsOnMethods = "shouldThrowNotFoundExceptionIfPairWithSuchNameOwnerAndServiceDoesNotExist") + public void shouldRemoveSshKeyPair() throws Exception { + final SshPairImpl pair = pairs[4]; + + try { + sshDao.remove(pair.getOwner(), pair.getService(), pair.getName()); + } catch (NotFoundException x) { + fail("SshKeyPair should be removed"); + } + + sshDao.get(pair.getOwner(), pair.getService(), pair.getName()); + } + + @Test + public void shouldGetSshPairByOwner() throws Exception { + final List sshPairs = sshDao.get(pairs[0].getOwner()); + + assertEquals(new HashSet<>(sshPairs), new HashSet<>(asList(pairs[0], pairs[1], pairs[2]))); + } + + @Test(expectedExceptions = NullPointerException.class) + public void shouldThrowNpeWhenGettingByNullOwner() throws Exception { + sshDao.get(null); + } + + @Test(expectedExceptions = NotFoundException.class) + public void shouldThrowNotFoundExceptionWhenRemovingNonExistingPair() throws Exception { + sshDao.remove(pairs[4].getService(), pairs[4].getService(), pairs[4].getService()); + } + + @Test(expectedExceptions = NullPointerException.class) + public void shouldThrowNpeOnRemoveWhenOwnerIsNull() throws Exception { + sshDao.remove(null, "service", "name"); + } + + @Test(expectedExceptions = NullPointerException.class) + public void shouldThrowNpeOnRemoveWhenServiceIsNull() throws Exception { + sshDao.remove("owner", null, "name"); + } + + @Test(expectedExceptions = NullPointerException.class) + public void shouldThrowNpeOnRemoveWhenNameIsNull() throws Exception { + sshDao.remove("owner", "service", null); + } +} diff --git a/wsmaster/che-core-api-ssh/src/test/resources/META-INF/persistence.xml b/wsmaster/che-core-api-ssh/src/test/resources/META-INF/persistence.xml new file mode 100644 index 0000000000..57239dcebf --- /dev/null +++ b/wsmaster/che-core-api-ssh/src/test/resources/META-INF/persistence.xml @@ -0,0 +1,36 @@ + + + + org.eclipse.persistence.jpa.PersistenceProvider + org.eclipse.che.api.ssh.server.model.impl.SshPairImpl + org.eclipse.che.account.spi.AccountImpl + org.eclipse.che.api.user.server.model.impl.UserImpl + true + + + + + + + + + + + + + + + diff --git a/wsmaster/che-core-api-ssh/src/test/resources/META-INF/services/org.eclipse.che.commons.test.tck.TckModule b/wsmaster/che-core-api-ssh/src/test/resources/META-INF/services/org.eclipse.che.commons.test.tck.TckModule new file mode 100644 index 0000000000..f4b169e32f --- /dev/null +++ b/wsmaster/che-core-api-ssh/src/test/resources/META-INF/services/org.eclipse.che.commons.test.tck.TckModule @@ -0,0 +1 @@ +org.eclipse.che.api.ssh.server.jpa.SshTckModule diff --git a/wsmaster/che-core-api-user/pom.xml b/wsmaster/che-core-api-user/pom.xml index 7ae013041c..056df3bd98 100644 --- a/wsmaster/che-core-api-user/pom.xml +++ b/wsmaster/che-core-api-user/pom.xml @@ -28,10 +28,18 @@ com.google.guava guava + + com.google.inject + guice + io.swagger swagger-annotations + + javax.annotation + javax.annotation-api + javax.inject javax.inject @@ -40,6 +48,10 @@ javax.ws.rs javax.ws.rs-api + + org.eclipse.che.core + che-core-api-account + org.eclipse.che.core che-core-api-core @@ -68,21 +80,51 @@ org.slf4j slf4j-api + + com.google.inject.extensions + guice-persist + provided + + + org.eclipse.che.core + che-core-api-jdbc + provided + + + org.eclipse.persistence + javax.persistence + provided + com.google.code.gson gson test + + com.h2database + h2 + test + com.jayway.restassured rest-assured test + + org.eclipse.che.core + che-core-api-jdbc-vendor-h2 + test + org.eclipse.che.core che-core-commons-json test + + org.eclipse.persistence + eclipselink + test + org.everrest everrest-assured @@ -114,16 +156,6 @@ - - - org.apache.maven.plugins - maven-surefire-plugin - - - **/spi/tck/*.* - - - org.apache.maven.plugins diff --git a/wsmaster/che-core-api-user/src/main/java/org/eclipse/che/api/user/server/CheUserCreator.java b/wsmaster/che-core-api-user/src/main/java/org/eclipse/che/api/user/server/CheUserCreator.java new file mode 100644 index 0000000000..48d9a3761e --- /dev/null +++ b/wsmaster/che-core-api-user/src/main/java/org/eclipse/che/api/user/server/CheUserCreator.java @@ -0,0 +1,58 @@ +/******************************************************************************* + * Copyright (c) 2012-2016 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 + *******************************************************************************/ +package org.eclipse.che.api.user.server; + +import org.eclipse.che.api.core.ConflictException; +import org.eclipse.che.api.core.NotFoundException; +import org.eclipse.che.api.core.ServerException; +import org.eclipse.che.api.core.jdbc.jpa.guice.JpaInitializer; +import org.eclipse.che.api.user.server.model.impl.UserImpl; + +import javax.annotation.PostConstruct; +import javax.inject.Inject; +import javax.inject.Singleton; + +import static java.util.Collections.emptyList; + +/** + * Creates 'che' default user. + * + * @author Anton Korneta + */ +@Singleton +public class CheUserCreator { + + @Inject + private UserManager userManager; + + @Inject + @SuppressWarnings("unused") + // this work around needed for Guice to help initialize components in right sequence, + // because instance of JpaInitializer should be created before components that dependent on dao (such as UserManager) + private JpaInitializer jpaInitializer; + + @PostConstruct + public void createCheUser() throws ServerException { + try { + userManager.getById("che"); + } catch (NotFoundException ex) { + try { + final UserImpl cheUser = new UserImpl("che", + "che@eclipse.org", + "che", + "secret", + emptyList()); + userManager.create(cheUser, false); + } catch (ConflictException ignore) { + } + } + } +} diff --git a/wsmaster/che-core-api-user/src/main/java/org/eclipse/che/api/user/server/DtoConverter.java b/wsmaster/che-core-api-user/src/main/java/org/eclipse/che/api/user/server/DtoConverter.java index c12a424387..bbc841e0a4 100644 --- a/wsmaster/che-core-api-user/src/main/java/org/eclipse/che/api/user/server/DtoConverter.java +++ b/wsmaster/che-core-api-user/src/main/java/org/eclipse/che/api/user/server/DtoConverter.java @@ -29,8 +29,7 @@ public final class DtoConverter { .withId(user.getId()) .withEmail(user.getEmail()) .withName(user.getName()) - .withAliases(user.getAliases()) - .withPassword(""); + .withAliases(user.getAliases()); } private DtoConverter() {} diff --git a/wsmaster/che-core-api-user/src/main/java/org/eclipse/che/api/user/server/ProfileService.java b/wsmaster/che-core-api-user/src/main/java/org/eclipse/che/api/user/server/ProfileService.java index aef2115761..13ad97b69a 100644 --- a/wsmaster/che-core-api-user/src/main/java/org/eclipse/che/api/user/server/ProfileService.java +++ b/wsmaster/che-core-api-user/src/main/java/org/eclipse/che/api/user/server/ProfileService.java @@ -108,9 +108,7 @@ public class ProfileService extends Service { Map updates) throws NotFoundException, ServerException, BadRequestException { - if (updates == null) { - throw new BadRequestException("Update attributes required"); - } + checkAttributes(updates); final ProfileImpl profile = new ProfileImpl(profileManager.getById(userId)); profile.setAttributes(updates); profileManager.update(profile); @@ -129,9 +127,7 @@ public class ProfileService extends Service { Map updates) throws NotFoundException, ServerException, BadRequestException { - if (updates == null) { - throw new BadRequestException("Update attributes required"); - } + checkAttributes(updates); final ProfileImpl profile = new ProfileImpl(profileManager.getById(userId())); profile.setAttributes(updates); profileManager.update(profile); @@ -160,6 +156,17 @@ public class ProfileService extends Service { profileManager.update(profile); } + private void checkAttributes(Map attributes) throws BadRequestException { + if (attributes == null) { + throw new BadRequestException("Update attributes required"); + } + for (String value : attributes.values()) { + if (value == null) { + throw new BadRequestException("Update attributes must not be null"); + } + } + } + private static ProfileDto asDto(Profile profile, User user) { return DtoFactory.newDto(ProfileDto.class) .withUserId(profile.getUserId()) diff --git a/wsmaster/che-core-api-user/src/main/java/org/eclipse/che/api/user/server/UserManager.java b/wsmaster/che-core-api-user/src/main/java/org/eclipse/che/api/user/server/UserManager.java index 3cd30c8ab9..3070d94c79 100644 --- a/wsmaster/che-core-api-user/src/main/java/org/eclipse/che/api/user/server/UserManager.java +++ b/wsmaster/che-core-api-user/src/main/java/org/eclipse/che/api/user/server/UserManager.java @@ -15,6 +15,7 @@ import com.google.common.collect.Sets; import org.eclipse.che.api.core.ConflictException; import org.eclipse.che.api.core.NotFoundException; +import org.eclipse.che.api.core.Page; import org.eclipse.che.api.core.ServerException; import org.eclipse.che.api.core.model.user.Profile; import org.eclipse.che.api.core.model.user.User; @@ -33,6 +34,7 @@ import javax.inject.Singleton; import java.util.Set; import static com.google.common.base.MoreObjects.firstNonNull; +import static com.google.common.base.Preconditions.checkArgument; import static java.lang.String.format; import static java.lang.System.currentTimeMillis; import static java.util.Objects.requireNonNull; @@ -60,7 +62,7 @@ public class UserManager { public UserManager(UserDao userDao, ProfileDao profileDao, PreferenceDao preferencesDao, - @Named("user.reserved_names") String[] reservedNames) { + @Named("che.account.reserved_names") String[] reservedNames) { this.userDao = userDao; this.profileDao = profileDao; this.preferencesDao = preferencesDao; @@ -84,7 +86,8 @@ public class UserManager { if (reservedNames.contains(newUser.getName().toLowerCase())) { throw new ConflictException(String.format("Username '%s' is reserved", newUser.getName())); } - final UserImpl user = new UserImpl(generate("user", ID_LENGTH), + final String userId = newUser.getId() != null ? newUser.getId() : generate("user", ID_LENGTH); + final UserImpl user = new UserImpl(userId, newUser.getEmail(), newUser.getName(), firstNonNull(newUser.getPassword(), generate("", PASSWORD_LENGTH)), @@ -207,7 +210,37 @@ public class UserManager { } /** - * Removes user and his dependencies by given {@code id}. + * Finds all users {@code email}. + * + * @param maxItems + * the maximum number of users to return + * @param skipCount + * the number of users to skip + * @return user instance + * @throws IllegalArgumentException + * when {@code maxItems} or {@code skipCount} is negative + * @throws ServerException + * when any other error occurs + */ + public Page getAll(int maxItems, int skipCount) throws ServerException { + checkArgument(maxItems >= 0, "The number of items to return can't be negative."); + checkArgument(skipCount >= 0, "The number of items to skip can't be negative."); + return userDao.getAll(maxItems, skipCount); + } + + /** + * Gets total count of all users + * + * @return user count + * @throws ServerException + * when any error occurs + */ + public long getTotalCount() throws ServerException { + return userDao.getTotalCount(); + } + + /** + * Removes user by given {@code id}. * * @param id * user identifier @@ -220,8 +253,6 @@ public class UserManager { */ public void remove(String id) throws ServerException, ConflictException { requireNonNull(id, "Required non-null id"); - profileDao.remove(id); - preferencesDao.remove(id); userDao.remove(id); } } diff --git a/wsmaster/che-core-api-user/src/main/java/org/eclipse/che/api/user/server/UserService.java b/wsmaster/che-core-api-user/src/main/java/org/eclipse/che/api/user/server/UserService.java index a47fff2dd7..2d70939387 100644 --- a/wsmaster/che-core-api-user/src/main/java/org/eclipse/che/api/user/server/UserService.java +++ b/wsmaster/che-core-api-user/src/main/java/org/eclipse/che/api/user/server/UserService.java @@ -104,6 +104,10 @@ public class UserService extends Service { UnauthorizedException, ConflictException, ServerException { + if (userDto != null) { + //should be generated by userManager + userDto.setId(null); + } final User newUser = token == null ? userDto : tokenValidator.validateToken(token); userValidator.checkUser(newUser); return Response.status(CREATED) diff --git a/wsmaster/che-core-api-user/src/main/java/org/eclipse/che/api/user/server/UserValidator.java b/wsmaster/che-core-api-user/src/main/java/org/eclipse/che/api/user/server/UserValidator.java index 45c132f0f1..0048728958 100644 --- a/wsmaster/che-core-api-user/src/main/java/org/eclipse/che/api/user/server/UserValidator.java +++ b/wsmaster/che-core-api-user/src/main/java/org/eclipse/che/api/user/server/UserValidator.java @@ -10,37 +10,33 @@ *******************************************************************************/ package org.eclipse.che.api.user.server; +import com.google.common.annotations.VisibleForTesting; + +import org.eclipse.che.account.spi.AccountValidator; import org.eclipse.che.api.core.BadRequestException; -import org.eclipse.che.api.core.NotFoundException; import org.eclipse.che.api.core.ServerException; import org.eclipse.che.api.core.model.user.User; -import org.eclipse.che.commons.lang.NameGenerator; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; import javax.inject.Inject; -import java.util.regex.Pattern; import static com.google.common.base.Strings.isNullOrEmpty; -// TODO extract normalization code from the validator as it is not related to the validation at all /** * Utils for username validation and normalization. * * @author Mihail Kuznyetsov * @author Yevhenii Voevodin + * @author Sergii Leschenko */ public class UserValidator { - private static final Logger LOG = LoggerFactory.getLogger(UserValidator.class); + @VisibleForTesting + final static String GENERATED_NAME_PREFIX = "username"; - private static final Pattern ILLEGAL_USERNAME_CHARACTERS = Pattern.compile("[^a-zA-Z0-9]"); - private static final Pattern VALID_USERNAME = Pattern.compile("^[a-zA-Z0-9]*"); - - private final UserManager userManager; + private final AccountValidator accountValidator; @Inject - public UserValidator(UserManager userManager) { - this.userManager = userManager; + public UserValidator(AccountValidator accountValidator) { + this.accountValidator = accountValidator; } /** @@ -106,7 +102,7 @@ public class UserValidator { * @return true if valid name, false otherwise */ public boolean isValidName(String name) { - return name != null && VALID_USERNAME.matcher(name).matches(); + return accountValidator.isValidName(name); } /** @@ -119,27 +115,6 @@ public class UserValidator { * @return username without illegal characters */ public String normalizeUserName(String name) throws ServerException { - String normalized = ILLEGAL_USERNAME_CHARACTERS.matcher(name).replaceAll(""); - String candidate = normalized.isEmpty() ? NameGenerator.generate("username", 4) : normalized; - - int i = 1; - try { - while (userExists(candidate)) { - candidate = normalized.isEmpty() ? NameGenerator.generate("username", 4) : normalized + String.valueOf(i++); - } - } catch (ServerException e) { - LOG.warn("Error occurred during username normalization", e); - throw e; - } - return candidate; - } - - private boolean userExists(String username) throws ServerException { - try { - userManager.getByName(username); - } catch (NotFoundException e) { - return false; - } - return true; + return accountValidator.normalizeAccountName(name, GENERATED_NAME_PREFIX); } } diff --git a/wsmaster/che-core-api-user/src/main/java/org/eclipse/che/api/user/server/event/BeforeUserRemovedEvent.java b/wsmaster/che-core-api-user/src/main/java/org/eclipse/che/api/user/server/event/BeforeUserRemovedEvent.java new file mode 100644 index 0000000000..4146661640 --- /dev/null +++ b/wsmaster/che-core-api-user/src/main/java/org/eclipse/che/api/user/server/event/BeforeUserRemovedEvent.java @@ -0,0 +1,32 @@ +/******************************************************************************* + * Copyright (c) 2012-2016 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 + *******************************************************************************/ +package org.eclipse.che.api.user.server.event; + +import org.eclipse.che.api.user.server.model.impl.UserImpl; + +/** + * Published before {@link UserImpl user} removed. + * + * @author Yevhenii Voevodin + */ +public class BeforeUserRemovedEvent { + + private final UserImpl user; + + public BeforeUserRemovedEvent(UserImpl user) { + this.user = user; + } + + /** Returns user which is going to be removed. */ + public UserImpl getUser() { + return user; + } +} diff --git a/wsmaster/che-core-api-user/src/main/java/org/eclipse/che/api/user/server/jpa/JpaPreferenceDao.java b/wsmaster/che-core-api-user/src/main/java/org/eclipse/che/api/user/server/jpa/JpaPreferenceDao.java new file mode 100644 index 0000000000..6b119fef15 --- /dev/null +++ b/wsmaster/che-core-api-user/src/main/java/org/eclipse/che/api/user/server/jpa/JpaPreferenceDao.java @@ -0,0 +1,163 @@ +/******************************************************************************* + * Copyright (c) 2012-2016 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 + *******************************************************************************/ +package org.eclipse.che.api.user.server.jpa; + +import com.google.inject.persist.Transactional; + +import org.eclipse.che.api.core.ServerException; +import org.eclipse.che.api.core.notification.EventService; +import org.eclipse.che.api.core.notification.EventSubscriber; +import org.eclipse.che.api.user.server.event.BeforeUserRemovedEvent; +import org.eclipse.che.api.user.server.spi.PreferenceDao; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import javax.annotation.PostConstruct; +import javax.annotation.PreDestroy; +import javax.inject.Inject; +import javax.inject.Provider; +import javax.inject.Singleton; +import javax.persistence.EntityManager; +import java.util.HashMap; +import java.util.Map; +import java.util.regex.Pattern; +import java.util.stream.Collectors; + +import static java.lang.String.format; +import static java.util.Objects.requireNonNull; + +/** + * Implementation of {@link PreferenceDao}. + * + * @author Anton Korneta + */ +@Singleton +public class JpaPreferenceDao implements PreferenceDao { + + private static final Logger LOG = LoggerFactory.getLogger(JpaPreferenceDao.class); + + @Inject + private Provider managerProvider; + + @Override + public void setPreferences(String userId, Map preferences) throws ServerException { + requireNonNull(userId); + requireNonNull(preferences); + final PreferenceEntity prefs = new PreferenceEntity(userId, preferences); + if (preferences.isEmpty()) { + remove(userId); + } else { + try { + doSetPreference(prefs); + } catch (RuntimeException ex) { + throw new ServerException(ex.getLocalizedMessage(), ex); + } + } + } + + @Override + @Transactional + public Map getPreferences(String userId) throws ServerException { + requireNonNull(userId); + try { + final EntityManager manager = managerProvider.get(); + final PreferenceEntity prefs = manager.find(PreferenceEntity.class, userId); + return prefs == null ? new HashMap<>() + : prefs.getPreferences(); + } catch (RuntimeException ex) { + throw new ServerException(ex.getLocalizedMessage(), ex); + } + } + + @Override + @Transactional + public Map getPreferences(String userId, String filter) throws ServerException { + requireNonNull(userId); + requireNonNull(filter); + try { + final EntityManager manager = managerProvider.get(); + final PreferenceEntity prefs = manager.find(PreferenceEntity.class, userId); + if (prefs == null) { + return new HashMap<>(); + } + final Map preferences = prefs.getPreferences(); + if (!filter.isEmpty()) { + final Pattern pattern = Pattern.compile(filter); + return preferences.entrySet() + .stream() + .filter(preference -> pattern.matcher(preference.getKey()).matches()) + .collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue)); + } else { + return preferences; + } + } catch (RuntimeException ex) { + throw new ServerException(ex.getLocalizedMessage(), ex); + } + } + + @Override + public void remove(String userId) throws ServerException { + requireNonNull(userId); + try { + doRemove(userId); + } catch (RuntimeException ex) { + throw new ServerException(ex); + } + } + + @Transactional + protected void doSetPreference(PreferenceEntity prefs) { + final EntityManager manager = managerProvider.get(); + final PreferenceEntity existing = manager.find(PreferenceEntity.class, prefs.getUserId()); + if (existing != null) { + manager.merge(prefs); + } else { + manager.persist(prefs); + } + } + + @Transactional + protected void doRemove(String userId) { + final EntityManager manager = managerProvider.get(); + final PreferenceEntity prefs = manager.find(PreferenceEntity.class, userId); + if (prefs != null) { + manager.remove(prefs); + } + } + + @Singleton + public static class RemovePreferencesBeforeUserRemovedEventSubscriber implements EventSubscriber { + + @Inject + private EventService eventService; + @Inject + private JpaPreferenceDao preferenceDao; + + @PostConstruct + public void subscribe() { + eventService.subscribe(this); + } + + @PreDestroy + public void unsubscribe() { + eventService.unsubscribe(this); + } + + @Override + public void onEvent(BeforeUserRemovedEvent event) { + try { + preferenceDao.remove(event.getUser().getId()); + } catch (Exception x) { + LOG.error(format("Couldn't remove preferences before user '%s' is removed", event.getUser().getId()), x); + } + } + } +} diff --git a/wsmaster/che-core-api-user/src/main/java/org/eclipse/che/api/user/server/jpa/JpaProfileDao.java b/wsmaster/che-core-api-user/src/main/java/org/eclipse/che/api/user/server/jpa/JpaProfileDao.java new file mode 100644 index 0000000000..ec66ec9b63 --- /dev/null +++ b/wsmaster/che-core-api-user/src/main/java/org/eclipse/che/api/user/server/jpa/JpaProfileDao.java @@ -0,0 +1,147 @@ +/******************************************************************************* + * Copyright (c) 2012-2016 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 + *******************************************************************************/ +package org.eclipse.che.api.user.server.jpa; + +import com.google.inject.persist.Transactional; + +import org.eclipse.che.api.core.ConflictException; +import org.eclipse.che.api.core.NotFoundException; +import org.eclipse.che.api.core.ServerException; +import org.eclipse.che.api.core.jdbc.jpa.DuplicateKeyException; +import org.eclipse.che.api.core.jdbc.jpa.IntegrityConstraintViolationException; +import org.eclipse.che.api.core.notification.EventService; +import org.eclipse.che.api.core.notification.EventSubscriber; +import org.eclipse.che.api.user.server.event.BeforeUserRemovedEvent; +import org.eclipse.che.api.user.server.model.impl.ProfileImpl; +import org.eclipse.che.api.user.server.spi.ProfileDao; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import javax.annotation.PostConstruct; +import javax.annotation.PreDestroy; +import javax.inject.Inject; +import javax.inject.Provider; +import javax.inject.Singleton; +import javax.persistence.EntityManager; + +import static java.lang.String.format; +import static java.util.Objects.requireNonNull; + +@Singleton +public class JpaProfileDao implements ProfileDao { + + private static final Logger LOG = LoggerFactory.getLogger(JpaProfileDao.class); + + @Inject + private Provider managerProvider; + + @Override + public void create(ProfileImpl profile) throws ServerException, ConflictException { + requireNonNull(profile, "Required non-null profile"); + try { + doCreate(profile); + } catch (DuplicateKeyException x) { + throw new ConflictException(format("Profile for user with id '%s' already exists", profile.getUserId())); + } catch (IntegrityConstraintViolationException x) { + throw new ConflictException(format("User with id '%s' referenced by profile doesn't exist", profile.getUserId())); + } catch (RuntimeException x) { + throw new ServerException(x.getLocalizedMessage(), x); + } + } + + @Override + public void update(ProfileImpl profile) throws NotFoundException, ServerException { + requireNonNull(profile, "Required non-null profile"); + try { + doUpdate(profile); + } catch (RuntimeException x) { + throw new ServerException(x.getLocalizedMessage(), x); + } + } + + @Override + public void remove(String id) throws ServerException { + requireNonNull(id, "Required non-null id"); + try { + doRemove(id); + } catch (RuntimeException x) { + throw new ServerException(x.getLocalizedMessage(), x); + } + } + + @Override + @Transactional + public ProfileImpl getById(String userId) throws NotFoundException, ServerException { + requireNonNull(userId, "Required non-null id"); + try { + final EntityManager manager = managerProvider.get(); + final ProfileImpl profile = manager.find(ProfileImpl.class, userId); + if (profile == null) { + throw new NotFoundException(format("Couldn't find profile for user with id '%s'", userId)); + } + manager.refresh(profile); + return profile; + } catch (RuntimeException x) { + throw new ServerException(x.getLocalizedMessage(), x); + } + } + + @Transactional + protected void doCreate(ProfileImpl profile) { + managerProvider.get().persist(profile); + } + + @Transactional + protected void doUpdate(ProfileImpl profile) throws NotFoundException { + final EntityManager manager = managerProvider.get(); + if (manager.find(ProfileImpl.class, profile.getUserId()) == null) { + throw new NotFoundException(format("Couldn't update profile, because profile for user with id '%s' doesn't exist", + profile.getUserId())); + } + manager.merge(profile); + } + + @Transactional + protected void doRemove(String userId) { + final EntityManager manager = managerProvider.get(); + final ProfileImpl profile = manager.find(ProfileImpl.class, userId); + if (profile != null) { + manager.remove(profile); + } + } + + @Singleton + public static class RemoveProfileBeforeUserRemovedEventSubscriber implements EventSubscriber { + @Inject + private EventService eventService; + @Inject + private JpaProfileDao profileDao; + + @PostConstruct + public void subscribe() { + eventService.subscribe(this); + } + + @PreDestroy + public void unsubscribe() { + eventService.unsubscribe(this); + } + + @Override + public void onEvent(BeforeUserRemovedEvent event) { + try { + profileDao.remove(event.getUser().getId()); + } catch (Exception x) { + LOG.error(format("Couldn't remove profile before user '%s' is removed", event.getUser().getId()), x); + } + } + } +} diff --git a/wsmaster/che-core-api-user/src/main/java/org/eclipse/che/api/user/server/jpa/JpaUserDao.java b/wsmaster/che-core-api-user/src/main/java/org/eclipse/che/api/user/server/jpa/JpaUserDao.java new file mode 100644 index 0000000000..c3be0ecbbd --- /dev/null +++ b/wsmaster/che-core-api-user/src/main/java/org/eclipse/che/api/user/server/jpa/JpaUserDao.java @@ -0,0 +1,242 @@ +/******************************************************************************* + * Copyright (c) 2012-2016 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 + *******************************************************************************/ +package org.eclipse.che.api.user.server.jpa; + +import com.google.inject.persist.Transactional; + +import org.eclipse.che.api.core.ConflictException; +import org.eclipse.che.api.core.NotFoundException; +import org.eclipse.che.api.core.Page; +import org.eclipse.che.api.core.ServerException; +import org.eclipse.che.api.core.jdbc.jpa.DuplicateKeyException; +import org.eclipse.che.api.user.server.model.impl.UserImpl; +import org.eclipse.che.api.user.server.spi.UserDao; +import org.eclipse.che.security.PasswordEncryptor; + +import javax.inject.Inject; +import javax.inject.Provider; +import javax.inject.Singleton; +import javax.persistence.EntityManager; +import javax.persistence.NoResultException; +import java.util.List; + +import static com.google.common.base.Preconditions.checkArgument; +import static java.lang.String.format; +import static java.util.Objects.requireNonNull; +import static java.util.stream.Collectors.toList; + +/** + * JPA based implementation of {@link UserDao}. + * + * @author Yevhenii Voevodin + * @author Anton Korneta + */ +@Singleton +public class JpaUserDao implements UserDao { + + @Inject + protected Provider managerProvider; + @Inject + private PasswordEncryptor encryptor; + + @Override + @Transactional + public UserImpl getByAliasAndPassword(String emailOrName, String password) throws NotFoundException, ServerException { + requireNonNull(emailOrName, "Required non-null email or name"); + requireNonNull(password, "Required non-null password"); + try { + final UserImpl user = managerProvider.get() + .createNamedQuery("User.getByAliasAndPassword", UserImpl.class) + .setParameter("alias", emailOrName) + .getSingleResult(); + if (!encryptor.test(password, user.getPassword())) { + throw new NotFoundException(format("User with email or name '%s' and given password doesn't exist", emailOrName)); + } + return erasePassword(user); + } catch (NoResultException x) { + throw new NotFoundException(format("User with email or name '%s' and given password doesn't exist", emailOrName)); + } catch (RuntimeException x) { + throw new ServerException(x.getLocalizedMessage(), x); + } + } + + @Override + public void create(UserImpl user) throws ConflictException, ServerException { + requireNonNull(user, "Required non-null user"); + try { + if (user.getPassword() != null) { + user.setPassword(encryptor.encrypt(user.getPassword())); + } + doCreate(user); + } catch (DuplicateKeyException x) { + // TODO make more concrete + throw new ConflictException("User with such id/name/email/alias already exists"); + } catch (RuntimeException x) { + throw new ServerException(x.getLocalizedMessage(), x); + } + } + + @Override + public void update(UserImpl update) throws NotFoundException, ServerException, ConflictException { + requireNonNull(update, "Required non-null update"); + try { + doUpdate(update); + } catch (DuplicateKeyException x) { + // TODO make more concrete + throw new ConflictException("User with such name/email/alias already exists"); + } catch (RuntimeException x) { + throw new ServerException(x.getLocalizedMessage(), x); + } + } + + @Override + public void remove(String id) throws ServerException, ConflictException { + requireNonNull(id, "Required non-null id"); + try { + doRemove(id); + } catch (RuntimeException x) { + throw new ServerException(x.getLocalizedMessage(), x); + } + } + + @Override + @Transactional + public UserImpl getByAlias(String alias) throws NotFoundException, ServerException { + requireNonNull(alias, "Required non-null alias"); + try { + return erasePassword(managerProvider.get() + .createNamedQuery("User.getByAlias", UserImpl.class) + .setParameter("alias", alias) + .getSingleResult()); + } catch (NoResultException x) { + throw new NotFoundException(format("User with alias '%s' doesn't exist", alias)); + } catch (RuntimeException x) { + throw new ServerException(x.getLocalizedMessage(), x); + } + } + + @Override + @Transactional + public UserImpl getById(String id) throws NotFoundException, ServerException { + requireNonNull(id, "Required non-null id"); + try { + final UserImpl user = managerProvider.get().find(UserImpl.class, id); + if (user == null) { + throw new NotFoundException(format("User with id '%s' doesn't exist", id)); + } + return erasePassword(user); + } catch (RuntimeException x) { + throw new ServerException(x.getLocalizedMessage(), x); + } + } + + @Override + @Transactional + public UserImpl getByName(String name) throws NotFoundException, ServerException { + requireNonNull(name, "Required non-null name"); + try { + return erasePassword(managerProvider.get() + .createNamedQuery("User.getByName", UserImpl.class) + .setParameter("name", name) + .getSingleResult()); + } catch (NoResultException x) { + throw new NotFoundException(format("User with name '%s' doesn't exist", name)); + } catch (RuntimeException x) { + throw new ServerException(x.getLocalizedMessage(), x); + } + } + + @Override + @Transactional + public UserImpl getByEmail(String email) throws NotFoundException, ServerException { + requireNonNull(email, "Required non-null email"); + try { + return erasePassword(managerProvider.get() + .createNamedQuery("User.getByEmail", UserImpl.class) + .setParameter("email", email) + .getSingleResult()); + } catch (NoResultException x) { + throw new NotFoundException(format("User with email '%s' doesn't exist", email)); + } catch (RuntimeException x) { + throw new ServerException(x.getLocalizedMessage(), x); + } + } + + @Override + @Transactional + public Page getAll(int maxItems, int skipCount) throws ServerException { + // TODO need to ensure that 'getAll' query works with same data as 'getTotalCount' + checkArgument(maxItems >= 0, "The number of items to return can't be negative."); + checkArgument(skipCount >= 0, "The number of items to skip can't be negative."); + try { + final List list = managerProvider.get() + .createNamedQuery("User.getAll", UserImpl.class) + .setMaxResults(maxItems) + .setFirstResult(skipCount) + .getResultList() + .stream() + .map(JpaUserDao::erasePassword) + .collect(toList()); + return new Page<>(list, skipCount, maxItems, getTotalCount()); + } catch (RuntimeException x) { + throw new ServerException(x.getLocalizedMessage(), x); + } + } + + @Override + @Transactional + public long getTotalCount() throws ServerException { + try { + return managerProvider.get().createNamedQuery("User.getTotalCount", Long.class).getSingleResult(); + } catch (RuntimeException x) { + throw new ServerException(x.getLocalizedMessage(), x); + } + } + + @Transactional + protected void doCreate(UserImpl user) { + managerProvider.get().persist(user); + } + + @Transactional + protected void doUpdate(UserImpl update) throws NotFoundException { + final EntityManager manager = managerProvider.get(); + final UserImpl user = manager.find(UserImpl.class, update.getId()); + if (user == null) { + throw new NotFoundException(format("Couldn't update user with id '%s' because it doesn't exist", update.getId())); + } + final String password = update.getPassword(); + if (password != null) { + update.setPassword(encryptor.encrypt(password)); + } else { + update.setPassword(user.getPassword()); + } + manager.merge(update); + } + + @Transactional + protected void doRemove(String id) { + final EntityManager manager = managerProvider.get(); + final UserImpl user = manager.find(UserImpl.class, id); + if (user != null) { + manager.remove(user); + } + } + + // Returns user instance copy without password + private static UserImpl erasePassword(UserImpl source) { + return new UserImpl(source.getId(), + source.getEmail(), + source.getName(), + null, + source.getAliases()); + } +} diff --git a/wsmaster/che-core-api-user/src/main/java/org/eclipse/che/api/user/server/jpa/PreferenceEntity.java b/wsmaster/che-core-api-user/src/main/java/org/eclipse/che/api/user/server/jpa/PreferenceEntity.java new file mode 100644 index 0000000000..698b97f4d9 --- /dev/null +++ b/wsmaster/che-core-api-user/src/main/java/org/eclipse/che/api/user/server/jpa/PreferenceEntity.java @@ -0,0 +1,97 @@ +/******************************************************************************* + * Copyright (c) 2012-2016 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 + *******************************************************************************/ +package org.eclipse.che.api.user.server.jpa; + +import org.eclipse.che.api.user.server.model.impl.UserImpl; + +import javax.persistence.Column; +import javax.persistence.ElementCollection; +import javax.persistence.Entity; +import javax.persistence.Id; +import javax.persistence.Lob; +import javax.persistence.MapKeyColumn; +import javax.persistence.PrimaryKeyJoinColumn; +import java.util.HashMap; +import java.util.Map; +import java.util.Objects; + +/** + * Describes JPA implementation of user's preferences. + * + * @author Anton Korneta + */ +@Entity(name = "Preference") +public class PreferenceEntity { + + @Id + private String userId; + + @PrimaryKeyJoinColumn + private UserImpl user; + + @ElementCollection + @MapKeyColumn(name = "name") + @Column(name = "value", columnDefinition = "TEXT") + private Map preferences; + + public PreferenceEntity() {} + + public PreferenceEntity(String userId, Map preferences) { + this.userId = userId; + this.preferences = preferences; + } + + public String getUserId() { + return userId; + } + + public void setUserId(String userId) { + this.userId = userId; + } + + public Map getPreferences() { + if (preferences == null) { + return new HashMap<>(); + } + return preferences; + } + + public void setPreferences(Map preferences) { + this.preferences = preferences; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) return true; + if (!(obj instanceof PreferenceEntity)) return false; + + final PreferenceEntity other = (PreferenceEntity)obj; + + return Objects.equals(userId, other.userId) + && getPreferences().equals(other.getPreferences()); + } + + @Override + public int hashCode() { + int hash = 7; + hash = 31 * hash + Objects.hashCode(userId); + hash = 31 * hash + getPreferences().hashCode(); + return hash; + } + + @Override + public String toString() { + return "PreferenceEntity{" + + "userId='" + userId + '\'' + + ", preferences=" + preferences + + '}'; + } +} diff --git a/wsmaster/wsmaster-local/src/test/java/org/eclipse/che/api/local/LocalUserTckRepository.java b/wsmaster/che-core-api-user/src/main/java/org/eclipse/che/api/user/server/jpa/UserEntityListener.java similarity index 56% rename from wsmaster/wsmaster-local/src/test/java/org/eclipse/che/api/local/LocalUserTckRepository.java rename to wsmaster/che-core-api-user/src/main/java/org/eclipse/che/api/user/server/jpa/UserEntityListener.java index 0159e543b3..944cf39793 100644 --- a/wsmaster/wsmaster-local/src/test/java/org/eclipse/che/api/local/LocalUserTckRepository.java +++ b/wsmaster/che-core-api-user/src/main/java/org/eclipse/che/api/user/server/jpa/UserEntityListener.java @@ -8,31 +8,29 @@ * Contributors: * Codenvy, S.A. - initial API and implementation *******************************************************************************/ -package org.eclipse.che.api.local; +package org.eclipse.che.api.user.server.jpa; +import org.eclipse.che.api.core.notification.EventService; +import org.eclipse.che.api.user.server.event.BeforeUserRemovedEvent; import org.eclipse.che.api.user.server.model.impl.UserImpl; -import org.eclipse.che.commons.test.tck.repository.TckRepository; import javax.inject.Inject; -import java.util.Collection; +import javax.inject.Singleton; +import javax.persistence.PreRemove; /** + * Callback for {@link UserImpl user} jpa related events. + * * @author Yevhenii Voevodin */ -public class LocalUserTckRepository implements TckRepository { +@Singleton +public class UserEntityListener { @Inject - private LocalUserDaoImpl userDao; + private EventService eventService; - @Override - public void createAll(Collection entities) { - for (UserImpl user : entities) { - userDao.users.put(user.getId(), new UserImpl(user)); - } - } - - @Override - public void removeAll() { - userDao.users.clear(); + @PreRemove + public void preRemove(UserImpl user) { + eventService.publish(new BeforeUserRemovedEvent(user)); } } diff --git a/wsmaster/che-core-api-user/src/main/java/org/eclipse/che/api/user/server/jpa/UserJpaModule.java b/wsmaster/che-core-api-user/src/main/java/org/eclipse/che/api/user/server/jpa/UserJpaModule.java new file mode 100644 index 0000000000..a00ce6e416 --- /dev/null +++ b/wsmaster/che-core-api-user/src/main/java/org/eclipse/che/api/user/server/jpa/UserJpaModule.java @@ -0,0 +1,37 @@ +/******************************************************************************* + * Copyright (c) 2012-2016 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 + *******************************************************************************/ +package org.eclipse.che.api.user.server.jpa; + +import com.google.inject.AbstractModule; + +import org.eclipse.che.api.user.server.jpa.JpaPreferenceDao.RemovePreferencesBeforeUserRemovedEventSubscriber; +import org.eclipse.che.api.user.server.jpa.JpaProfileDao.RemoveProfileBeforeUserRemovedEventSubscriber; +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.security.PBKDF2PasswordEncryptor; +import org.eclipse.che.security.PasswordEncryptor; + +/** + * @author Yevhenii Voevodin + */ +public class UserJpaModule extends AbstractModule { + + @Override + protected void configure() { + bind(PasswordEncryptor.class).to(PBKDF2PasswordEncryptor.class); + bind(UserDao.class).to(JpaUserDao.class); + bind(ProfileDao.class).to(JpaProfileDao.class); + bind(PreferenceDao.class).to(JpaPreferenceDao.class); + bind(RemoveProfileBeforeUserRemovedEventSubscriber.class).asEagerSingleton(); + bind(RemovePreferencesBeforeUserRemovedEventSubscriber.class).asEagerSingleton(); + } +} diff --git a/wsmaster/che-core-api-user/src/main/java/org/eclipse/che/api/user/server/model/impl/ProfileImpl.java b/wsmaster/che-core-api-user/src/main/java/org/eclipse/che/api/user/server/model/impl/ProfileImpl.java index a8d5dbd412..89a26b350e 100644 --- a/wsmaster/che-core-api-user/src/main/java/org/eclipse/che/api/user/server/model/impl/ProfileImpl.java +++ b/wsmaster/che-core-api-user/src/main/java/org/eclipse/che/api/user/server/model/impl/ProfileImpl.java @@ -12,6 +12,15 @@ package org.eclipse.che.api.user.server.model.impl; import org.eclipse.che.api.core.model.user.Profile; +import javax.persistence.CollectionTable; +import javax.persistence.Column; +import javax.persistence.ElementCollection; +import javax.persistence.Entity; +import javax.persistence.Id; +import javax.persistence.JoinColumn; +import javax.persistence.MapKeyColumn; +import javax.persistence.PrimaryKeyJoinColumn; +import javax.persistence.UniqueConstraint; import java.util.HashMap; import java.util.Map; import java.util.Objects; @@ -21,17 +30,30 @@ import java.util.Objects; * * @author Yevhenii Voevodin */ +@Entity(name = "Profile") public class ProfileImpl implements Profile { - private String id; + @Id + private String userId; + + @PrimaryKeyJoinColumn + private UserImpl user; + + @ElementCollection + @MapKeyColumn(name = "name") + @Column(name = "value", nullable = false) + @CollectionTable(joinColumns = @JoinColumn(name = "user_id"), + uniqueConstraints = @UniqueConstraint(columnNames = {"user_id", "name"})) private Map attributes; - public ProfileImpl(String id) { - this.id = id; + public ProfileImpl() {} + + public ProfileImpl(String userId) { + this.userId = userId; } - public ProfileImpl(String id, Map attributes) { - this.id = id; + public ProfileImpl(String userId, Map attributes) { + this.userId = userId; if (attributes != null) { this.attributes = new HashMap<>(attributes); } @@ -41,9 +63,18 @@ public class ProfileImpl implements Profile { this(profile.getUserId(), profile.getAttributes()); } + public ProfileImpl(ProfileImpl profile) { + this(profile.getUserId(), profile.getAttributes()); + this.user = profile.user; + } + @Override public String getUserId() { - return id; + return userId; + } + + public void setUserId(String userId) { + this.userId = userId; } @Override @@ -54,6 +85,10 @@ public class ProfileImpl implements Profile { return attributes; } + public UserImpl getUser() { + return user; + } + public void setAttributes(Map attributes) { this.attributes = attributes; } @@ -67,13 +102,13 @@ public class ProfileImpl implements Profile { return false; } final ProfileImpl that = (ProfileImpl)obj; - return Objects.equals(id, that.id) && getAttributes().equals(that.getAttributes()); + return Objects.equals(userId, that.userId) && getAttributes().equals(that.getAttributes()); } @Override public int hashCode() { int hash = 7; - hash = 31 * hash + Objects.hashCode(id); + hash = 31 * hash + Objects.hashCode(userId); hash = 31 * hash + getAttributes().hashCode(); return hash; } @@ -81,7 +116,7 @@ public class ProfileImpl implements Profile { @Override public String toString() { return "ProfileImpl{" + - "id='" + id + '\'' + + "userId='" + userId + '\'' + ", attributes=" + attributes + '}'; } diff --git a/wsmaster/che-core-api-user/src/main/java/org/eclipse/che/api/user/server/model/impl/UserImpl.java b/wsmaster/che-core-api-user/src/main/java/org/eclipse/che/api/user/server/model/impl/UserImpl.java index 951a822e60..4d8e6ad9d5 100644 --- a/wsmaster/che-core-api-user/src/main/java/org/eclipse/che/api/user/server/model/impl/UserImpl.java +++ b/wsmaster/che-core-api-user/src/main/java/org/eclipse/che/api/user/server/model/impl/UserImpl.java @@ -10,8 +10,24 @@ *******************************************************************************/ package org.eclipse.che.api.user.server.model.impl; +import org.eclipse.che.account.spi.AccountImpl; import org.eclipse.che.api.core.model.user.User; +import org.eclipse.che.api.user.server.jpa.UserEntityListener; +import javax.persistence.Basic; +import javax.persistence.CascadeType; +import javax.persistence.CollectionTable; +import javax.persistence.Column; +import javax.persistence.ElementCollection; +import javax.persistence.Entity; +import javax.persistence.EntityListeners; +import javax.persistence.Id; +import javax.persistence.Index; +import javax.persistence.JoinColumn; +import javax.persistence.NamedQueries; +import javax.persistence.NamedQuery; +import javax.persistence.OneToOne; +import javax.persistence.Table; import java.util.ArrayList; import java.util.Collection; import java.util.List; @@ -22,22 +38,61 @@ import java.util.Objects; * * @author Yevhenii Voevodin */ -public class UserImpl implements User { +@Entity(name = "Usr") +@NamedQueries( + { + @NamedQuery(name = "User.getByAliasAndPassword", + query = "SELECT u " + + "FROM Usr u " + + "WHERE :alias = u.account.name OR" + + " :alias = u.email"), + @NamedQuery(name = "User.getByAlias", + query = "SELECT u FROM Usr u WHERE :alias MEMBER OF u.aliases"), + @NamedQuery(name = "User.getByName", + query = "SELECT u FROM Usr u WHERE u.account.name = :name"), + @NamedQuery(name = "User.getByEmail", + query = "SELECT u FROM Usr u WHERE u.email = :email"), + @NamedQuery(name = "User.getAll", + query = "SELECT u FROM Usr u"), + @NamedQuery(name = "User.getTotalCount", + query = "SELECT COUNT(u) FROM Usr u") - private String id; - private String email; - private String name; - private String password; + } +) +@EntityListeners(UserEntityListener.class) +@Table(indexes = {@Index(columnList = "email", unique = true)}) +public class UserImpl implements User { + public static final String PERSONAL_ACCOUNT = "personal"; + + @Id + private String id; + + @OneToOne(cascade = CascadeType.ALL) + @JoinColumn(nullable = false) + private AccountImpl account; + + @Column(nullable = false) + private String email; + + @Basic + private String password; + + @ElementCollection + @Column(name = "alias", nullable = false, unique = true) + @CollectionTable(name = "user_aliases", + indexes = @Index(columnList = "alias"), + joinColumns = @JoinColumn(name = "user_id")) private List aliases; - public UserImpl(String id) { - this.id = id; + public UserImpl() { + this.account = new AccountImpl(); + account.setType(PERSONAL_ACCOUNT); } public UserImpl(String id, String email, String name) { + this.account = new AccountImpl(id, name, PERSONAL_ACCOUNT); this.id = id; this.email = email; - this.name = name; } public UserImpl(String id, @@ -65,6 +120,13 @@ public class UserImpl implements User { return id; } + public void setId(String id) { + this.id = id; + if (account != null) { + account.setId(id); + } + } + @Override public String getEmail() { return email; @@ -76,11 +138,16 @@ public class UserImpl implements User { @Override public String getName() { - return name; + if (account != null) { + return account.getName(); + } + return null; } public void setName(String name) { - this.name = name; + if (account != null) { + account.setName(name); + } } @Override @@ -104,6 +171,14 @@ public class UserImpl implements User { this.aliases = aliases; } + public AccountImpl getAccount() { + return account; + } + + public void setAccount(AccountImpl account) { + this.account = account; + } + @Override public boolean equals(Object obj) { if (this == obj) { @@ -115,7 +190,7 @@ public class UserImpl implements User { final UserImpl that = (UserImpl)obj; return Objects.equals(id, that.id) && Objects.equals(email, that.email) - && Objects.equals(name, that.name) + && Objects.equals(getName(), that.getName()) && Objects.equals(password, that.password) && getAliases().equals(that.getAliases()); } @@ -125,7 +200,7 @@ public class UserImpl implements User { int hash = 7; hash = 31 * hash + Objects.hashCode(id); hash = 31 * hash + Objects.hashCode(email); - hash = 31 * hash + Objects.hashCode(name); + hash = 31 * hash + Objects.hashCode(getName()); hash = 31 * hash + Objects.hashCode(password); hash = 31 * hash + getAliases().hashCode(); return hash; @@ -136,7 +211,7 @@ public class UserImpl implements User { return "UserImpl{" + "id='" + id + '\'' + ", email='" + email + '\'' + - ", name='" + name + '\'' + + ", name='" + getName() + '\'' + ", password='" + password + '\'' + ", aliases=" + aliases + '}'; diff --git a/wsmaster/che-core-api-user/src/main/java/org/eclipse/che/api/user/server/spi/UserDao.java b/wsmaster/che-core-api-user/src/main/java/org/eclipse/che/api/user/server/spi/UserDao.java index 949371d452..1611892122 100644 --- a/wsmaster/che-core-api-user/src/main/java/org/eclipse/che/api/user/server/spi/UserDao.java +++ b/wsmaster/che-core-api-user/src/main/java/org/eclipse/che/api/user/server/spi/UserDao.java @@ -12,8 +12,8 @@ package org.eclipse.che.api.user.server.spi; import org.eclipse.che.api.core.ConflictException; import org.eclipse.che.api.core.NotFoundException; +import org.eclipse.che.api.core.Page; import org.eclipse.che.api.core.ServerException; -import org.eclipse.che.api.core.UnauthorizedException; import org.eclipse.che.api.user.server.model.impl.UserImpl; /** @@ -33,22 +33,21 @@ import org.eclipse.che.api.user.server.model.impl.UserImpl; public interface UserDao { /** - * // TODO remove this method from spi - * Authenticates user. + * Gets user by email or name and password * - * @param emailOrAliasOrName - * one of the user identifiers such as email/name/alias + * @param emailOrName + * one of user attribute such as email/name(but not id) * @param password * password * @return user identifier * @throws NullPointerException - * when either {@code emailOrAliasOrName} or {@code password} is null - * @throws UnauthorizedException - * when user with such {@code aliasOrName} and {@code password} doesn't exist + * when either {@code emailOrName} or {@code password} is null + * @throws NotFoundException + * when user with such {@code emailOrName} and {@code password} doesn't exist * @throws ServerException * when any other error occurs */ - String authenticate(String emailOrAliasOrName, String password) throws UnauthorizedException, ServerException; + UserImpl getByAliasAndPassword(String emailOrName, String password) throws NotFoundException, ServerException; /** * Creates a new user. @@ -163,4 +162,28 @@ public interface UserDao { * when any other error occurs */ UserImpl getByEmail(String email) throws NotFoundException, ServerException; + + /** + * Gets all users from persistent layer. + * + * @param maxItems + * the maximum number of users to return + * @param skipCount + * the number of users to skip + * @return list of users POJO or empty list if no users were found + * @throws IllegalArgumentException + * when {@code maxItems} or {@code skipCount} is negative + * @throws ServerException + * when any other error occurs + */ + Page getAll(int maxItems, int skipCount) throws ServerException; + + /** + * Get count of all users from persistent layer. + * + * @return user count + * @throws ServerException + * when any error occurs + */ + long getTotalCount() throws ServerException; } diff --git a/wsmaster/che-core-api-user/src/test/java/org/eclipse/che/api/user/server/UserManagerTest.java b/wsmaster/che-core-api-user/src/test/java/org/eclipse/che/api/user/server/UserManagerTest.java index d590abfcea..270396cfd5 100644 --- a/wsmaster/che-core-api-user/src/test/java/org/eclipse/che/api/user/server/UserManagerTest.java +++ b/wsmaster/che-core-api-user/src/test/java/org/eclipse/che/api/user/server/UserManagerTest.java @@ -11,6 +11,7 @@ package org.eclipse.che.api.user.server; import org.eclipse.che.api.core.ConflictException; +import org.eclipse.che.api.core.Page; import org.eclipse.che.api.core.ServerException; import org.eclipse.che.api.core.model.user.User; import org.eclipse.che.api.user.server.model.impl.ProfileImpl; @@ -26,6 +27,7 @@ import org.testng.annotations.DataProvider; import org.testng.annotations.Listeners; import org.testng.annotations.Test; +import java.util.Arrays; import java.util.Collections; import java.util.concurrent.Callable; @@ -112,7 +114,19 @@ public class UserManagerTest { } @Test - public void shouldGenerateIdentifierWhenCreatingUser() throws Exception { + public void shouldGenerateIdentifierWhenCreatingUserWithNullId() throws Exception { + final User user = new UserImpl(null, "test@email.com", "testName", null, null); + + manager.create(user, false); + + final ArgumentCaptor userCaptor = ArgumentCaptor.forClass(UserImpl.class); + verify(userDao).create(userCaptor.capture()); + final String id = userCaptor.getValue().getId(); + assertNotNull(id); + } + + @Test + public void shouldNotGenerateIdentifierWhenCreatingUserWithNotNullId() throws Exception { final User user = new UserImpl("identifier", "test@email.com", "testName", null, null); manager.create(user, false); @@ -121,7 +135,7 @@ public class UserManagerTest { verify(userDao).create(userCaptor.capture()); final String id = userCaptor.getValue().getId(); assertNotNull(id); - assertNotEquals(id, "identifier"); + assertEquals(id, "identifier"); } @Test(expectedExceptions = NullPointerException.class) @@ -191,6 +205,36 @@ public class UserManagerTest { assertEquals(manager.getByEmail(user.getEmail()), user); } + @Test + public void shouldGetTotalUserCount() throws Exception { + when(userDao.getTotalCount()).thenReturn(5L); + + assertEquals(manager.getTotalCount(), 5); + verify(userDao).getTotalCount(); + } + + @Test + public void shouldGetAllUsers() throws Exception { + final Page users = new Page(Arrays.asList( + new UserImpl("identifier1", "test1@email.com", "testName1", "password", Collections.singletonList("alias1")), + new UserImpl("identifier2", "test2@email.com", "testName2", "password", Collections.singletonList("alias2")), + new UserImpl("identifier3", "test3@email.com", "testName3", "password", Collections.singletonList("alias3"))), 0, 30, 3); + when(userDao.getAll(30, 0)).thenReturn(users); + + assertEquals(manager.getAll(30, 0), users); + verify(userDao).getAll(30, 0); + } + + @Test(expectedExceptions = IllegalArgumentException.class) + public void shouldThrowIllegalArgumentExceptionsWhenGetAllUsersWithNegativeMaxItems() throws Exception { + manager.getAll(-5, 0); + } + + @Test(expectedExceptions = IllegalArgumentException.class) + public void shouldThrowIllegalArgumentExceptionsWhenGetAllUsersWithNegativeSkipCount() throws Exception { + manager.getAll(30, -11); + } + @Test(expectedExceptions = NullPointerException.class) public void shouldThrowNpeWhenRemovingUserByNullId() throws Exception { manager.remove(null); @@ -201,8 +245,6 @@ public class UserManagerTest { manager.remove("user123"); verify(userDao).remove("user123"); - verify(preferencesDao).remove("user123"); - verify(profileDao).remove("user123"); } @Test(expectedExceptions = ConflictException.class) diff --git a/wsmaster/che-core-api-user/src/test/java/org/eclipse/che/api/user/server/UserServiceTest.java b/wsmaster/che-core-api-user/src/test/java/org/eclipse/che/api/user/server/UserServiceTest.java index 36a1742c13..ad1d30d91f 100644 --- a/wsmaster/che-core-api-user/src/test/java/org/eclipse/che/api/user/server/UserServiceTest.java +++ b/wsmaster/che-core-api-user/src/test/java/org/eclipse/che/api/user/server/UserServiceTest.java @@ -15,6 +15,8 @@ import com.google.gson.Gson; import com.google.gson.reflect.TypeToken; import com.jayway.restassured.response.Response; +import org.eclipse.che.account.api.AccountManager; +import org.eclipse.che.account.spi.AccountValidator; import org.eclipse.che.api.core.ConflictException; import org.eclipse.che.api.core.model.user.User; import org.eclipse.che.api.core.rest.ApiExceptionMapper; @@ -70,6 +72,8 @@ public class UserServiceTest { @Mock(answer = Answers.RETURNS_MOCKS) private UserManager userManager; @Mock + private AccountManager accountManager; + @Mock private TokenValidator tokenValidator; @Mock private UserLinksInjector linksInjector; @@ -82,7 +86,7 @@ public class UserServiceTest { public void initService() { initMocks(this); - userValidator = new UserValidator(userManager); + userValidator = new UserValidator(new AccountValidator(accountManager)); // Return the incoming instance when injectLinks is called when(linksInjector.injectLinks(any(), any())).thenAnswer(inv -> inv.getArguments()[0]); diff --git a/wsmaster/che-core-api-user/src/test/java/org/eclipse/che/api/user/server/UserValidatorTest.java b/wsmaster/che-core-api-user/src/test/java/org/eclipse/che/api/user/server/UserValidatorTest.java index c3d486b3e7..48af69611e 100644 --- a/wsmaster/che-core-api-user/src/test/java/org/eclipse/che/api/user/server/UserValidatorTest.java +++ b/wsmaster/che-core-api-user/src/test/java/org/eclipse/che/api/user/server/UserValidatorTest.java @@ -10,75 +10,54 @@ *******************************************************************************/ package org.eclipse.che.api.user.server; -import org.eclipse.che.api.core.NotFoundException; +import org.eclipse.che.account.spi.AccountValidator; import org.mockito.InjectMocks; import org.mockito.Mock; import org.mockito.testng.MockitoTestNGListener; -import org.testng.Assert; -import org.testng.annotations.DataProvider; import org.testng.annotations.Listeners; import org.testng.annotations.Test; +import static org.mockito.Matchers.any; import static org.mockito.Matchers.anyString; -import static org.mockito.Mockito.doThrow; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; +import static org.testng.Assert.assertEquals; /** * Tests of {@link UserValidator}. * - * @author Mihail Kuznyetsov - * @author Yevhenii Voevodin + * @author Sergii Leschenko */ @Listeners(MockitoTestNGListener.class) public class UserValidatorTest { @Mock - private UserManager userManager; + private AccountValidator accountValidator; @InjectMocks private UserValidator userNameValidator; - @Test(dataProvider = "normalizeNames") - public void testNormalizeUserName(String input, String expected) throws Exception { - doThrow(NotFoundException.class).when(userManager).getByName(anyString()); + @Test + public void shouldReturnNameNormalizedByAccountValidator() throws Exception { + when(accountValidator.normalizeAccountName(anyString(), anyString())).thenReturn("testname"); - Assert.assertEquals(userNameValidator.normalizeUserName(input), expected); + assertEquals(userNameValidator.normalizeUserName("toNormalize"), "testname"); + verify(accountValidator).normalizeAccountName("toNormalize", UserValidator.GENERATED_NAME_PREFIX); } + @Test + public void shouldReturnTrueWhenInputIsValidAccountName() throws Exception { + when(accountValidator.isValidName(any())).thenReturn(true); - @Test(dataProvider = "validNames") - public void testValidUserName(String input, boolean expected) throws Exception { - doThrow(NotFoundException.class).when(userManager).getByName(anyString()); - - Assert.assertEquals(userNameValidator.isValidName(input), expected); + assertEquals(userNameValidator.isValidName("toTest"), true); + verify(accountValidator).isValidName("toTest"); } - @DataProvider(name = "normalizeNames") - public Object[][] normalizeNames() { - return new Object[][] {{"test", "test"}, - {"test123", "test123"}, - {"test 123", "test123"}, - {"test@gmail.com", "testgmailcom"}, - {"TEST", "TEST"}, - {"test-", "test"}, - {"te-st", "test"}, - {"-test", "test"}, - {"te_st", "test"}, - {"te#st", "test"} - }; - } + @Test + public void shouldReturnFalseWhenInputIsInvalidAccountName() throws Exception { + when(accountValidator.isValidName(any())).thenReturn(false); - @DataProvider(name = "validNames") - public Object[][] validNames() { - return new Object[][] {{"test", true}, - {"test123", true}, - {"test 123", false}, - {"test@gmail.com", false}, - {"TEST", true}, - {"test-", false}, - {"te-st", false}, - {"-test", false}, - {"te_st", false}, - {"te#st", false} - }; + assertEquals(userNameValidator.isValidName("toTest"), false); + verify(accountValidator).isValidName("toTest"); } } diff --git a/wsmaster/che-core-api-user/src/test/java/org/eclipse/che/api/user/server/jpa/JpaTckModule.java b/wsmaster/che-core-api-user/src/test/java/org/eclipse/che/api/user/server/jpa/JpaTckModule.java new file mode 100644 index 0000000000..ba380f2571 --- /dev/null +++ b/wsmaster/che-core-api-user/src/test/java/org/eclipse/che/api/user/server/jpa/JpaTckModule.java @@ -0,0 +1,54 @@ +/******************************************************************************* + * Copyright (c) 2012-2016 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 + *******************************************************************************/ +package org.eclipse.che.api.user.server.jpa; + +import com.google.inject.Singleton; +import com.google.inject.TypeLiteral; +import com.google.inject.persist.jpa.JpaPersistModule; + +import org.eclipse.che.api.core.jdbc.jpa.eclipselink.EntityListenerInjectionManagerInitializer; +import org.eclipse.che.api.core.jdbc.jpa.guice.JpaInitializer; +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.commons.lang.Pair; +import org.eclipse.che.commons.test.tck.TckModule; +import org.eclipse.che.commons.test.tck.repository.JpaTckRepository; +import org.eclipse.che.commons.test.tck.repository.TckRepository; +import org.eclipse.che.security.PasswordEncryptor; +import org.eclipse.che.security.SHA512PasswordEncryptor; + +import java.util.Map; + +/** + * @author Yevhenii Voevodin + */ +public class JpaTckModule extends TckModule { + + @Override + protected void configure() { + install(new JpaPersistModule("main")); + bind(JpaInitializer.class).asEagerSingleton(); + bind(EntityListenerInjectionManagerInitializer.class).asEagerSingleton(); + + bind(new TypeLiteral>() {}).to(UserJpaTckRepository.class); + bind(new TypeLiteral>() {}).toInstance(new JpaTckRepository<>(ProfileImpl.class)); + bind(new TypeLiteral>>>() {}).to(PreferenceJpaTckRepository.class); + + bind(UserDao.class).to(JpaUserDao.class); + bind(ProfileDao.class).to(JpaProfileDao.class); + bind(PreferenceDao.class).to(JpaPreferenceDao.class); + // SHA-512 ecnryptor is faster than PBKDF2 so it is better for testing + bind(PasswordEncryptor.class).to(SHA512PasswordEncryptor.class).in(Singleton.class); + } +} diff --git a/wsmaster/che-core-api-user/src/test/java/org/eclipse/che/api/user/server/jpa/PreferenceJpaTckRepository.java b/wsmaster/che-core-api-user/src/test/java/org/eclipse/che/api/user/server/jpa/PreferenceJpaTckRepository.java new file mode 100644 index 0000000000..f92c75b688 --- /dev/null +++ b/wsmaster/che-core-api-user/src/test/java/org/eclipse/che/api/user/server/jpa/PreferenceJpaTckRepository.java @@ -0,0 +1,53 @@ +/******************************************************************************* + * Copyright (c) 2012-2016 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 + *******************************************************************************/ +package org.eclipse.che.api.user.server.jpa; + +import com.google.inject.persist.Transactional; + +import org.eclipse.che.api.user.server.model.impl.UserImpl; +import org.eclipse.che.commons.lang.Pair; +import org.eclipse.che.commons.test.tck.repository.TckRepository; +import org.eclipse.che.commons.test.tck.repository.TckRepositoryException; + +import javax.inject.Inject; +import javax.inject.Provider; +import javax.persistence.EntityManager; +import java.util.Collection; +import java.util.Collections; +import java.util.Map; + +/** + * Implementation of {@link TckRepository}. + * + * @author Anton Korneta + */ +@Transactional +public class PreferenceJpaTckRepository implements TckRepository>> { + + @Inject + private Provider managerProvider; + + @Override + public void createAll(Collection>> entities) throws TckRepositoryException { + final EntityManager manager = managerProvider.get(); + for (Pair> pair : entities) { + manager.persist(new PreferenceEntity(pair.first, pair.second)); + } + } + + @Override + public void removeAll() throws TckRepositoryException { + final EntityManager manager = managerProvider.get(); + manager.createQuery("SELECT prefs FROM Preference prefs", PreferenceEntity.class) + .getResultList() + .forEach(manager::remove); + } +} diff --git a/wsmaster/che-core-api-user/src/test/java/org/eclipse/che/api/user/server/jpa/UserJpaTckRepository.java b/wsmaster/che-core-api-user/src/test/java/org/eclipse/che/api/user/server/jpa/UserJpaTckRepository.java new file mode 100644 index 0000000000..d92e698fc8 --- /dev/null +++ b/wsmaster/che-core-api-user/src/test/java/org/eclipse/che/api/user/server/jpa/UserJpaTckRepository.java @@ -0,0 +1,54 @@ +/******************************************************************************* + * Copyright (c) 2012-2016 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 + *******************************************************************************/ +package org.eclipse.che.api.user.server.jpa; + +import com.google.inject.persist.Transactional; + +import org.eclipse.che.api.user.server.model.impl.UserImpl; +import org.eclipse.che.commons.test.tck.repository.TckRepository; +import org.eclipse.che.commons.test.tck.repository.TckRepositoryException; +import org.eclipse.che.security.PasswordEncryptor; + +import javax.inject.Inject; +import javax.inject.Provider; +import javax.persistence.EntityManager; +import javax.persistence.EntityManagerFactory; +import java.util.Collection; + +@Transactional +public 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(), + 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); + } +} diff --git a/wsmaster/che-core-api-user/src/test/java/org/eclipse/che/api/user/server/spi/tck/PreferenceDaoTest.java b/wsmaster/che-core-api-user/src/test/java/org/eclipse/che/api/user/server/spi/tck/PreferenceDaoTest.java new file mode 100644 index 0000000000..7a33c9121a --- /dev/null +++ b/wsmaster/che-core-api-user/src/test/java/org/eclipse/che/api/user/server/spi/tck/PreferenceDaoTest.java @@ -0,0 +1,211 @@ +/******************************************************************************* + * Copyright (c) 2012-2016 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 + *******************************************************************************/ +package org.eclipse.che.api.user.server.spi.tck; + +import com.google.common.collect.ImmutableMap; + +import org.eclipse.che.api.user.server.model.impl.UserImpl; +import org.eclipse.che.api.user.server.spi.PreferenceDao; +import org.eclipse.che.commons.lang.Pair; +import org.eclipse.che.commons.test.tck.TckModuleFactory; +import org.eclipse.che.commons.test.tck.repository.TckRepository; +import org.testng.annotations.AfterMethod; +import org.testng.annotations.BeforeMethod; +import org.testng.annotations.Guice; +import org.testng.annotations.Test; + +import javax.inject.Inject; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import static java.util.Collections.emptyList; +import static java.util.Collections.emptyMap; +import static org.testng.Assert.assertEquals; +import static org.testng.Assert.assertTrue; + +/** + * Tests {@link PreferenceDao} contract. + * + * @author Anton Korneta + */ +@Guice(moduleFactory = TckModuleFactory.class) +@Test(suiteName = PreferenceDaoTest.SUITE_NAME) +public class PreferenceDaoTest { + + public static final String SUITE_NAME = "PreferenceDaoTck"; + + private static final int ENTRY_COUNT = 5; + + private List>> userPreferences; + + @Inject + private PreferenceDao preferenceDao; + @Inject + private TckRepository userTckRepository; + @Inject + private TckRepository>> preferenceTckRepository; + + @BeforeMethod + private void setUp() throws Exception { + userPreferences = new ArrayList<>(ENTRY_COUNT); + UserImpl[] users = new UserImpl[ENTRY_COUNT]; + + for (int index = 0; index < ENTRY_COUNT; index++) { + String userId = "userId_" + index; + users[index] = new UserImpl(userId, "email_" + userId, "name_" + userId, "password", emptyList()); + + final Map prefs = new HashMap<>(); + prefs.put("preference1", "value"); + prefs.put("preference2", "value"); + prefs.put("preference3", "value"); + userPreferences.add(Pair.of(userId, prefs)); + } + userTckRepository.createAll(Arrays.asList(users)); + preferenceTckRepository.createAll(userPreferences); + } + + @AfterMethod + private void cleanUp() throws Exception { + preferenceTckRepository.removeAll(); + userTckRepository.removeAll(); + } + + @Test(dependsOnMethods = {"shouldGetPreference", "shouldRemovePreference"}) + public void shouldSetPreference() throws Exception { + final String userId = userPreferences.get(0).first; + final Map prefs = ImmutableMap.of("key", "value"); + preferenceDao.remove(userId); + preferenceDao.setPreferences(userId, prefs); + + assertEquals(preferenceDao.getPreferences(userId), prefs); + } + + @Test(expectedExceptions = NullPointerException.class) + public void shouldThrowNpeWhenSetPreferenceUserNull() throws Exception { + preferenceDao.setPreferences(null, ImmutableMap.of("key", "value")); + } + + @Test(expectedExceptions = NullPointerException.class) + public void shouldThrowNpeWhenSetPreferenceUpdateNull() throws Exception { + preferenceDao.setPreferences(userPreferences.get(0).first, null); + } + + @Test(dependsOnMethods = "shouldGetPreference") + public void shouldOverridePreference() throws Exception { + final String userId = userPreferences.get(0).first; + final Map update = ImmutableMap.of("key", "value"); + preferenceDao.setPreferences(userId, update); + + assertEquals(preferenceDao.getPreferences(userId), update); + } + + @Test(dependsOnMethods = "shouldGetPreference") + public void shouldUpdatePreference() throws Exception { + final String userId = userPreferences.get(0).first; + final Map update = userPreferences.get(0).second; + userPreferences.get(0).second.put("preference4", "value"); + preferenceDao.setPreferences(userId, update); + + assertEquals(preferenceDao.getPreferences(userId), update); + } + + @Test(dependsOnMethods = "shouldGetPreference") + public void shouldRemovePreferenceWhenUpdateIsEmpty() throws Exception { + final String userId = userPreferences.get(0).first; + final Map update = emptyMap(); + preferenceDao.setPreferences(userId, update); + + assertEquals(preferenceDao.getPreferences(userId), update); + } + + @Test + public void shouldGetPreference() throws Exception { + final Pair> prefs = userPreferences.get(0); + + assertEquals(preferenceDao.getPreferences(prefs.first), prefs.second); + } + + @Test + public void shouldGetPreferenceWithFilter() throws Exception { + final Pair> prefs = userPreferences.get(0); + + assertEquals(preferenceDao.getPreferences(prefs.first, "\\w*"), prefs.second); + } + + @Test(expectedExceptions = NullPointerException.class) + public void shouldThrowNpeWhenGetPreferenceWithNullFilter() throws Exception { + preferenceDao.getPreferences(userPreferences.get(0).first, null); + } + + @Test(expectedExceptions = NullPointerException.class) + public void shouldThrowNpeWhenGetPreferenceWithFilterAndNullUser() throws Exception { + preferenceDao.getPreferences(null, "\\w*"); + } + + @Test + public void shouldReturnEmptyPreferenceMapWhenNoMatchedResults() throws Exception { + assertEquals(preferenceDao.getPreferences(userPreferences.get(0).first, "pattern"), emptyMap()); + } + + @Test(dependsOnMethods = "shouldRemovePreference") + public void shouldReturnEmptyPreferenceMapWhenNoPreferenceUserFound() throws Exception { + final String userId = userPreferences.get(0).first; + preferenceDao.remove(userId); + + assertEquals(preferenceDao.getPreferences(userId, "\\w*"), emptyMap()); + } + + @Test + public void shouldReturnPreferenceWhenFilterEmpty() throws Exception { + assertEquals(preferenceDao.getPreferences(userPreferences.get(0).first, ""), + userPreferences.get(0).second); + } + + @Test + public void shouldReturnFilteredPreferences() throws Exception { + final String userId = userPreferences.get(0).first; + final Map.Entry preference = userPreferences.get(0).second.entrySet() + .iterator() + .next(); + + assertEquals(preferenceDao.getPreferences(userId, preference.getKey()), + ImmutableMap.of(preference.getKey(), preference.getValue())); + } + + @Test(dependsOnMethods = {"shouldGetPreference", "shouldRemovePreferenceWhenUpdateIsEmpty"}) + public void shouldGetEmptyPreferenceMapWhenPreferenceForUserNotFound() throws Exception { + final String userId = userPreferences.get(0).first; + preferenceDao.setPreferences(userId, emptyMap()); + + assertTrue(preferenceDao.getPreferences(userId).isEmpty()); + } + + @Test(expectedExceptions = NullPointerException.class) + public void shouldThrowNpeWhenGetPreferenceUserNull() throws Exception { + preferenceDao.getPreferences(null); + } + + @Test(dependsOnMethods = "shouldGetPreference") + public void shouldRemovePreference() throws Exception { + final String userId = userPreferences.get(0).first; + preferenceDao.remove(userId); + + assertTrue(preferenceDao.getPreferences(userId).isEmpty()); + } + + @Test(expectedExceptions = NullPointerException.class) + public void shouldThrowNpeWhenRemovePreferenceUserNull() throws Exception { + preferenceDao.remove(null); + } +} diff --git a/wsmaster/che-core-api-user/src/test/java/org/eclipse/che/api/user/server/spi/tck/ProfileDaoTest.java b/wsmaster/che-core-api-user/src/test/java/org/eclipse/che/api/user/server/spi/tck/ProfileDaoTest.java index e634ef40ab..201d76b2cd 100644 --- a/wsmaster/che-core-api-user/src/test/java/org/eclipse/che/api/user/server/spi/tck/ProfileDaoTest.java +++ b/wsmaster/che-core-api-user/src/test/java/org/eclipse/che/api/user/server/spi/tck/ProfileDaoTest.java @@ -16,6 +16,7 @@ import org.eclipse.che.api.core.ConflictException; import org.eclipse.che.api.core.NotFoundException; import org.eclipse.che.api.user.server.Constants; 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.ProfileDao; import org.eclipse.che.commons.lang.NameGenerator; import org.eclipse.che.commons.test.tck.TckModuleFactory; @@ -31,6 +32,7 @@ import java.util.Arrays; import java.util.HashMap; import java.util.Map; +import static java.util.Collections.emptyList; import static org.testng.Assert.assertEquals; /** @@ -52,27 +54,33 @@ public class ProfileDaoTest { private ProfileDao profileDao; @Inject - private TckRepository tckRepository; + private TckRepository profileTckRepository; + @Inject + private TckRepository userTckRepository; @BeforeMethod private void setUp() throws TckRepositoryException { + UserImpl[] users = new UserImpl[COUNT_OF_PROFILES]; profiles = new ProfileImpl[COUNT_OF_PROFILES]; for (int i = 0; i < COUNT_OF_PROFILES; i++) { final String userId = NameGenerator.generate("user", Constants.ID_LENGTH); + users[i] = new UserImpl(userId, userId + "@eclipse.org", userId, "password", emptyList()); + final Map attributes = new HashMap<>(); attributes.put("firstName", "first-name-" + i); attributes.put("lastName", "last-name-" + i); attributes.put("company", "company-" + i); profiles[i] = new ProfileImpl(userId, attributes); } - - tckRepository.createAll(Arrays.asList(profiles)); + userTckRepository.createAll(Arrays.asList(users)); + profileTckRepository.createAll(Arrays.asList(profiles)); } @AfterMethod private void cleanup() throws TckRepositoryException { - tckRepository.removeAll(); + profileTckRepository.removeAll(); + userTckRepository.removeAll(); } @Test @@ -92,16 +100,14 @@ public class ProfileDaoTest { profileDao.getById(null); } - @Test(dependsOnMethods = "shouldGetProfileById") + @Test(dependsOnMethods = {"shouldGetProfileById", "shouldRemoveProfile"}) public void shouldCreateProfile() throws Exception { - final ProfileImpl newProfile = new ProfileImpl("user123", - ImmutableMap.of("attribute1", "value1", - "attribute2", "value2", - "attribute3", "value3")); + final ProfileImpl profile = profiles[0]; - profileDao.create(newProfile); + profileDao.remove(profile.getUserId()); + profileDao.create(profile); - assertEquals(profileDao.getById(newProfile.getUserId()), newProfile); + assertEquals(profileDao.getById(profile.getUserId()), profile); } @Test(expectedExceptions = ConflictException.class) diff --git a/wsmaster/che-core-api-user/src/test/java/org/eclipse/che/api/user/server/spi/tck/UserDaoTest.java b/wsmaster/che-core-api-user/src/test/java/org/eclipse/che/api/user/server/spi/tck/UserDaoTest.java index 8094d9ae1b..12a2ffaf4b 100644 --- a/wsmaster/che-core-api-user/src/test/java/org/eclipse/che/api/user/server/spi/tck/UserDaoTest.java +++ b/wsmaster/che-core-api-user/src/test/java/org/eclipse/che/api/user/server/spi/tck/UserDaoTest.java @@ -12,7 +12,7 @@ package org.eclipse.che.api.user.server.spi.tck; import org.eclipse.che.api.core.ConflictException; import org.eclipse.che.api.core.NotFoundException; -import org.eclipse.che.api.core.UnauthorizedException; +import org.eclipse.che.api.core.Page; import org.eclipse.che.api.core.model.user.User; import org.eclipse.che.api.user.server.Constants; import org.eclipse.che.api.user.server.model.impl.UserImpl; @@ -29,10 +29,13 @@ import org.testng.annotations.Test; import javax.inject.Inject; import java.util.ArrayList; import java.util.Arrays; +import java.util.HashSet; import java.util.List; import static java.util.Arrays.asList; import static org.testng.Assert.assertEquals; +import static org.testng.Assert.assertNotNull; +import static org.testng.AssertJUnit.assertTrue; /** * Tests {@link UserDao} contract. @@ -77,48 +80,41 @@ public class UserDaoTest { } @Test - public void shouldAuthenticateUserByName() throws Exception { + public void shouldGetUserByNameAndPassword() throws Exception { final UserImpl user = users[0]; - assertEquals(userDao.authenticate(user.getName(), user.getPassword()), user.getId()); + assertEqualsNoPassword(userDao.getByAliasAndPassword(user.getName(), user.getPassword()), user); } @Test - public void shouldAuthenticateUserByEmail() throws Exception { + public void shouldGetUserByEmailAndPassword() throws Exception { final UserImpl user = users[0]; - assertEquals(userDao.authenticate(user.getEmail(), user.getPassword()), user.getId()); + assertEqualsNoPassword(userDao.getByAliasAndPassword(user.getEmail(), user.getPassword()), user); } - @Test - public void shouldAuthenticateUserByAlias() throws Exception { + @Test(expectedExceptions = NotFoundException.class) + public void shouldThrowNotFoundExceptionIfUserWithSuchNameOrEmailDoesNotExist() throws Exception { final UserImpl user = users[0]; - assertEquals(userDao.authenticate(user.getAliases().get(0), user.getPassword()), user.getId()); + userDao.getByAliasAndPassword(user.getId(), user.getPassword()); } - @Test(expectedExceptions = UnauthorizedException.class) - public void shouldNotAuthenticateUserById() throws Exception { + @Test(expectedExceptions = NotFoundException.class) + public void shouldThrowNotFoundExceptionWhenGettingUserByNameAndWrongPassword() throws Exception { final UserImpl user = users[0]; - assertEquals(userDao.authenticate(user.getId(), user.getPassword()), user.getId()); - } - - @Test(expectedExceptions = UnauthorizedException.class) - public void shouldNotAuthenticateUserWithWrongPassword() throws Exception { - final UserImpl user = users[0]; - - assertEquals(userDao.authenticate(user.getName(), "fake" + user.getPassword()), user.getId()); + userDao.getByAliasAndPassword(user.getName(), "fake" + user.getPassword()); } @Test(expectedExceptions = NullPointerException.class) - public void shouldThrowNpeWhenAuthorizingUserWithNullAlias() throws Exception { - userDao.authenticate(null, "password"); + public void shouldThrowNpeWhenAuthorizingUserWithNullEmailOrName() throws Exception { + userDao.getByAliasAndPassword(null, "password"); } @Test(expectedExceptions = NullPointerException.class) public void shouldThrowNpeWhenAuthorizingUserWithNullPassword() throws Exception { - userDao.authenticate("alias", null); + userDao.getByAliasAndPassword(users[0].getName(), null); } @Test @@ -189,6 +185,55 @@ public class UserDaoTest { userDao.getByAlias(null); } + @Test + public void shouldGetTotalUserCount() throws Exception { + assertEquals(userDao.getTotalCount(), 5); + } + + @Test + public void getAllShouldReturnAllUsersWithinSingleResponse() throws Exception { + List result = userDao.getAll(6, 0).getItems(); + assertEquals(result.size(), 5); + + result.sort((User o1, User o2) -> o1.getName().compareTo(o2.getName())); + for (int i = 0; i < result.size(); i++) { + assertEqualsNoPassword(users[i], result.get(i)); + } + } + + @Test + public void shouldReturnGetAllWithSkipCountAndMaxItems() throws Exception { + List users = userDao.getAll(3, 0).getItems(); + assertEquals(users.size(), 3); + + users = userDao.getAll(3, 3).getItems(); + assertEquals(users.size(), 2); + } + + @Test + public void shouldReturnEmptyListIfNoMoreUsers() throws Exception { + List users = userDao.getAll(1, 6).getItems(); + assertTrue(users.isEmpty()); + } + + @Test(expectedExceptions = IllegalArgumentException.class) + public void getAllShouldThrowIllegalArgumentExceptionIfMaxItemsWrong() throws Exception { + userDao.getAll(-1, 5); + } + + @Test(expectedExceptions = IllegalArgumentException.class) + public void getAllShouldThrowIllegalArgumentExceptionIfSkipCountWrong() throws Exception { + userDao.getAll(2, -1); + } + + @Test(dependsOnMethods = "shouldGetTotalUserCount") + public void shouldReturnCorrectTotalCountAlongWithRequestedUsers() throws Exception { + final Page page = userDao.getAll(2, 0); + + assertEquals(page.getItems().size(), 2); + assertEquals(page.getTotalItemsCount(), 5); + } + @Test(dependsOnMethods = "shouldGetUserById") public void shouldCreateUser() throws Exception { final UserImpl newUser = new UserImpl("user123", @@ -258,14 +303,14 @@ public class UserDaoTest { userDao.update(new UserImpl(user.getId(), "new-email", "new-name", - "new-password", + null, asList("google:new-alias", "github:new-alias"))); final UserImpl updated = userDao.getById(user.getId()); assertEquals(updated.getId(), user.getId()); assertEquals(updated.getEmail(), "new-email"); assertEquals(updated.getName(), "new-name"); - assertEquals(updated.getAliases(), asList("google:new-alias", "github:new-alias")); + assertEquals(new HashSet<>(updated.getAliases()), new HashSet<>(asList("google:new-alias", "github:new-alias"))); } @Test(expectedExceptions = ConflictException.class) @@ -328,10 +373,48 @@ public class UserDaoTest { userDao.remove(null); } + @Test(dependsOnMethods = "shouldGetUserById") + public void shouldReturnUserWithNullPasswordWhenGetUserById() throws Exception { + assertEquals(userDao.getById(users[0].getId()).getPassword(), null); + } + + @Test(dependsOnMethods = "shouldGetUserByAlias") + public void shouldReturnUserWithNullPasswordWhenGetUserByAliases() throws Exception { + assertEquals(userDao.getByAlias(users[0].getAliases().get(0)).getPassword(), null); + } + + @Test(dependsOnMethods = "shouldGetUserByName") + public void shouldReturnUserWithNullPasswordWhenGetUserByName() throws Exception { + assertEquals(userDao.getByName(users[0].getName()).getPassword(), null); + } + + @Test(dependsOnMethods = "shouldGetUserByEmail") + public void shouldReturnUserWithNullPasswordWhenGetUserByEmail() throws Exception { + assertEquals(userDao.getByEmail(users[0].getEmail()).getPassword(), null); + } + + @Test(dependsOnMethods = {"shouldGetUserByNameAndPassword", + "shouldGetUserByEmailAndPassword"}) + public void shouldReturnUserWithNullPasswordWhenGetUserByAliasAndPassword() throws Exception { + final UserImpl user = users[0]; + assertEquals(userDao.getByAliasAndPassword(user.getName(), user.getPassword()).getPassword(), null); + assertEquals(userDao.getByAliasAndPassword(user.getEmail(), user.getPassword()).getPassword(), null); + } + + @Test(dependsOnMethods = "getAllShouldReturnAllUsersWithinSingleResponse") + public void shouldReturnUserWithNullPasswordWhenGetAllUser() throws Exception { + assertEquals(userDao.getAll(users.length, 0) + .getItems() + .stream() + .filter(u -> u.getPassword() == null) + .count(), users.length); + } + private static void assertEqualsNoPassword(User actual, User expected) { + assertNotNull(actual, "Expected not-null user"); assertEquals(actual.getId(), expected.getId()); assertEquals(actual.getEmail(), expected.getEmail()); assertEquals(actual.getName(), expected.getName()); - assertEquals(actual.getAliases(), expected.getAliases()); + assertEquals(new HashSet<>(actual.getAliases()), new HashSet<>(expected.getAliases())); } } diff --git a/wsmaster/che-core-api-user/src/test/resources/META-INF/persistence.xml b/wsmaster/che-core-api-user/src/test/resources/META-INF/persistence.xml new file mode 100644 index 0000000000..ad8838bff7 --- /dev/null +++ b/wsmaster/che-core-api-user/src/test/resources/META-INF/persistence.xml @@ -0,0 +1,37 @@ + + + + org.eclipse.persistence.jpa.PersistenceProvider + org.eclipse.che.account.spi.AccountImpl + org.eclipse.che.api.user.server.model.impl.UserImpl + org.eclipse.che.api.user.server.model.impl.ProfileImpl + org.eclipse.che.api.user.server.jpa.PreferenceEntity + true + + + + + + + + + + + + + + + diff --git a/wsmaster/che-core-api-user/src/test/resources/META-INF/services/org.eclipse.che.commons.test.tck.TckModule b/wsmaster/che-core-api-user/src/test/resources/META-INF/services/org.eclipse.che.commons.test.tck.TckModule new file mode 100644 index 0000000000..0c0e217515 --- /dev/null +++ b/wsmaster/che-core-api-user/src/test/resources/META-INF/services/org.eclipse.che.commons.test.tck.TckModule @@ -0,0 +1 @@ +org.eclipse.che.api.user.server.jpa.JpaTckModule diff --git a/wsmaster/che-core-api-workspace-shared/src/main/java/org/eclipse/che/api/workspace/shared/Constants.java b/wsmaster/che-core-api-workspace-shared/src/main/java/org/eclipse/che/api/workspace/shared/Constants.java index 2fdfd10620..d6853bf4e5 100644 --- a/wsmaster/che-core-api-workspace-shared/src/main/java/org/eclipse/che/api/workspace/shared/Constants.java +++ b/wsmaster/che-core-api-workspace-shared/src/main/java/org/eclipse/che/api/workspace/shared/Constants.java @@ -21,6 +21,7 @@ public final class Constants { public static final String AUTO_CREATE_SNAPSHOT = "auto_snapshot"; public static final String AUTO_RESTORE_FROM_SNAPSHOT = "auto_restore"; public static final String LINK_REL_GET_WORKSPACES = "get workspaces"; + public static final String LINK_REL_GET_BY_NAMESPACE = "get by namespace"; public static final String LINK_REL_CREATE_WORKSPACE = "create workspace"; public static final String LINK_REL_REMOVE_WORKSPACE = "remove workspace"; public static final String LINK_REL_START_WORKSPACE = "start workspace"; diff --git a/wsmaster/che-core-api-workspace/pom.xml b/wsmaster/che-core-api-workspace/pom.xml index 01a58a96d8..9903d5c807 100644 --- a/wsmaster/che-core-api-workspace/pom.xml +++ b/wsmaster/che-core-api-workspace/pom.xml @@ -75,11 +75,11 @@ org.eclipse.che.core - che-core-api-agent + che-core-api-account org.eclipse.che.core - che-core-api-agent-shared + che-core-api-agent org.eclipse.che.core @@ -129,6 +129,11 @@ org.slf4j slf4j-api + + com.google.inject.extensions + guice-persist + provided + javax.websocket javax.websocket-api @@ -139,16 +144,41 @@ javax.ws.rs-api provided + + org.eclipse.che.core + che-core-api-jdbc + provided + + + org.eclipse.persistence + eclipselink + provided + + + org.eclipse.persistence + javax.persistence + provided + ch.qos.logback logback-classic test + + com.h2database + h2 + test + com.jayway.restassured rest-assured test + + org.eclipse.che.core + che-core-api-jdbc-vendor-h2 + test + org.eclipse.che.core che-core-commons-test @@ -188,4 +218,25 @@ test + + + + + org.apache.maven.plugins + maven-jar-plugin + + + + test-jar + + + + **/spi/tck/*.* + + + + + + + diff --git a/wsmaster/che-core-api-workspace/src/main/java/org/eclipse/che/api/environment/server/CheEnvironmentEngine.java b/wsmaster/che-core-api-workspace/src/main/java/org/eclipse/che/api/environment/server/CheEnvironmentEngine.java index 7f8cfb4f24..58163d798d 100644 --- a/wsmaster/che-core-api-workspace/src/main/java/org/eclipse/che/api/environment/server/CheEnvironmentEngine.java +++ b/wsmaster/che-core-api-workspace/src/main/java/org/eclipse/che/api/environment/server/CheEnvironmentEngine.java @@ -40,7 +40,8 @@ import org.eclipse.che.api.environment.server.compose.model.ComposeEnvironmentIm import org.eclipse.che.api.environment.server.compose.model.ComposeServiceImpl; import org.eclipse.che.api.environment.server.exception.EnvironmentNotRunningException; import org.eclipse.che.api.machine.server.MachineInstanceProviders; -import org.eclipse.che.api.machine.server.dao.SnapshotDao; +import org.eclipse.che.api.machine.server.model.impl.MachineSourceImpl; +import org.eclipse.che.api.machine.server.spi.SnapshotDao; import org.eclipse.che.api.machine.server.event.InstanceStateEvent; import org.eclipse.che.api.machine.server.exception.MachineException; import org.eclipse.che.api.machine.server.exception.SourceNotFoundException; @@ -458,7 +459,6 @@ public class CheEnvironmentEngine { snapshot = SnapshotImpl.builder() .generateId() .setType(machine.getConfig().getType()) - .setNamespace(namespace) .setWorkspaceId(machine.getWorkspaceId()) .setDescription(machine.getEnvName()) .setDev(machine.getConfig().isDev()) @@ -475,7 +475,7 @@ public class CheEnvironmentEngine { } try { MachineSource machineSource = instance.saveToSnapshot(); - snapshot.setMachineSource(machineSource); + snapshot.setMachineSource(new MachineSourceImpl(machineSource)); return snapshot; } catch (ServerException e) { try { diff --git a/wsmaster/che-core-api-workspace/src/main/java/org/eclipse/che/api/workspace/server/DtoConverter.java b/wsmaster/che-core-api-workspace/src/main/java/org/eclipse/che/api/workspace/server/DtoConverter.java index 06a88105c9..22761dd959 100644 --- a/wsmaster/che-core-api-workspace/src/main/java/org/eclipse/che/api/workspace/server/DtoConverter.java +++ b/wsmaster/che-core-api-workspace/src/main/java/org/eclipse/che/api/workspace/server/DtoConverter.java @@ -210,7 +210,6 @@ public final class DtoConverter { .withCreationDate(snapshot.getCreationDate()) .withDescription(snapshot.getDescription()) .withDev(snapshot.isDev()) - .withNamespace(snapshot.getNamespace()) .withType(snapshot.getType()) .withWorkspaceId(snapshot.getWorkspaceId()) .withEnvName(snapshot.getEnvName()) diff --git a/wsmaster/che-core-api-workspace/src/main/java/org/eclipse/che/api/workspace/server/WorkspaceHooks.java b/wsmaster/che-core-api-workspace/src/main/java/org/eclipse/che/api/workspace/server/WorkspaceHooks.java deleted file mode 100644 index 1e20eb7184..0000000000 --- a/wsmaster/che-core-api-workspace/src/main/java/org/eclipse/che/api/workspace/server/WorkspaceHooks.java +++ /dev/null @@ -1,86 +0,0 @@ -/******************************************************************************* - * Copyright (c) 2012-2016 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 - *******************************************************************************/ -package org.eclipse.che.api.workspace.server; - -import org.eclipse.che.api.core.NotFoundException; -import org.eclipse.che.api.core.ForbiddenException; -import org.eclipse.che.api.core.ServerException; -import org.eclipse.che.api.core.model.workspace.Workspace; -import org.eclipse.che.commons.annotation.Nullable; - -/** - * Generic interface for methods called on particular workspace events if some additional actions needed. - * - *

The most common use-case - to register/unregister workspace in the account - * - * @author gazarenkov - * @author Eugene Voevodin - */ -public interface WorkspaceHooks { - - /** - * Called before workspace starting. - * - * @param workspace - * workspace which is going to be started - * @param accountId - * account identifier indicates the account which should be used for runtime workspace - * @param envName - * the name of environment which is going to be started - * @throws NotFoundException - * when any not found error occurs - * @throws ForbiddenException - * when user doesn't have access to start workspace in certain account - * @throws ServerException - * when any other error occurs - * @throws NullPointerException - * when either {@code workspace} or {@code envName} is null - */ - void beforeStart(Workspace workspace, String envName, @Nullable String accountId) throws NotFoundException, - ForbiddenException, - ServerException; - - /** - * Called before creating workspace. - * - * @param workspace - * workspace instance - * @param accountId - * related to workspace account identifier, it is optional and may be null - * @throws NotFoundException - * when any not found error occurs - * @throws ServerException - * when any other error occurs - */ - void beforeCreate(Workspace workspace, @Nullable String accountId) throws NotFoundException, ServerException; - - /** - * Called after workspace is created. - * - * @param workspace - * workspace which was created - * @param accountId - * related to workspace account identifier, it is optional and may be null - * @throws ServerException - * when any other error occurs - */ - void afterCreate(Workspace workspace, @Nullable String accountId) throws ServerException; - - /** - * Called after workspace is removed. - * - * @param workspaceId - * identifier of workspace which was removed - * @throws ServerException - * when any error occurs - */ - void afterRemove(String workspaceId) throws ServerException; -} diff --git a/wsmaster/che-core-api-workspace/src/main/java/org/eclipse/che/api/workspace/server/WorkspaceManager.java b/wsmaster/che-core-api-workspace/src/main/java/org/eclipse/che/api/workspace/server/WorkspaceManager.java index b60705f93f..60436a3612 100644 --- a/wsmaster/che-core-api-workspace/src/main/java/org/eclipse/che/api/workspace/server/WorkspaceManager.java +++ b/wsmaster/che-core-api-workspace/src/main/java/org/eclipse/che/api/workspace/server/WorkspaceManager.java @@ -14,6 +14,8 @@ import com.google.common.annotations.VisibleForTesting; import com.google.common.util.concurrent.ThreadFactoryBuilder; import com.google.inject.Inject; +import org.eclipse.che.account.api.AccountManager; +import org.eclipse.che.account.shared.model.Account; import org.eclipse.che.api.core.ApiException; import org.eclipse.che.api.core.BadRequestException; import org.eclipse.che.api.core.ConflictException; @@ -24,7 +26,7 @@ import org.eclipse.che.api.core.model.workspace.Workspace; import org.eclipse.che.api.core.model.workspace.WorkspaceConfig; import org.eclipse.che.api.core.model.workspace.WorkspaceStatus; import org.eclipse.che.api.core.notification.EventService; -import org.eclipse.che.api.machine.server.dao.SnapshotDao; +import org.eclipse.che.api.machine.server.spi.SnapshotDao; import org.eclipse.che.api.machine.server.model.impl.MachineImpl; import org.eclipse.che.api.machine.server.model.impl.SnapshotImpl; import org.eclipse.che.api.machine.server.spi.Instance; @@ -90,21 +92,22 @@ public class WorkspaceManager { private final WorkspaceRuntimes runtimes; private final EventService eventService; private final ExecutorService executor; + private final AccountManager accountManager; private final boolean defaultAutoSnapshot; private final boolean defaultAutoRestore; private final SnapshotDao snapshotDao; - private WorkspaceHooks hooks = new NoopWorkspaceHooks(); - @Inject public WorkspaceManager(WorkspaceDao workspaceDao, WorkspaceRuntimes workspaceRegistry, EventService eventService, + AccountManager accountManager, @Named("workspace.runtime.auto_snapshot") boolean defaultAutoSnapshot, @Named("workspace.runtime.auto_restore") boolean defaultAutoRestore, SnapshotDao snapshotDao) { this.workspaceDao = workspaceDao; this.runtimes = workspaceRegistry; + this.accountManager = accountManager; this.eventService = eventService; this.defaultAutoSnapshot = defaultAutoSnapshot; this.defaultAutoRestore = defaultAutoRestore; @@ -115,11 +118,6 @@ public class WorkspaceManager { .build()); } - @Inject(optional = true) - public void setHooks(WorkspaceHooks hooks) { - this.hooks = hooks; - } - /** * Creates a new {@link WorkspaceImpl} instance based on the given configuration. * @@ -127,9 +125,6 @@ public class WorkspaceManager { * the workspace config to create the new workspace instance * @param namespace * workspace name is unique in this namespace - * @param accountId - * the account id, which is used to verify if the user has required - * permissions to create the new workspace * @return new workspace instance * @throws NullPointerException * when either {@code config} or {@code owner} is null @@ -139,21 +134,17 @@ public class WorkspaceManager { * when any conflict occurs (e.g Workspace with such name already exists for {@code owner}) * @throws ServerException * when any other error occurs - * @see WorkspaceHooks#beforeCreate(Workspace, String) - * @see WorkspaceHooks#afterCreate(Workspace, String) */ public WorkspaceImpl createWorkspace(WorkspaceConfig config, - String namespace, - @Nullable String accountId) throws ServerException, - ConflictException, - NotFoundException { + String namespace) throws ServerException, + ConflictException, + NotFoundException { requireNonNull(config, "Required non-null config"); requireNonNull(namespace, "Required non-null namespace"); return normalizeState(doCreateWorkspace(config, - namespace, + accountManager.getByName(namespace), emptyMap(), - false, - accountId)); + false)); } /** @@ -166,9 +157,6 @@ public class WorkspaceManager { * workspace name is unique in this namespace * @param attributes * workspace instance attributes - * @param accountId - * the account id, which is used to verify if the user has required - * permissions to create the new workspace * @return new workspace instance * @throws NullPointerException * when either {@code config} or {@code owner} is null @@ -178,23 +166,19 @@ public class WorkspaceManager { * when any conflict occurs (e.g Workspace with such name already exists for {@code owner}) * @throws ServerException * when any other error occurs - * @see WorkspaceHooks#beforeCreate(Workspace, String) - * @see WorkspaceHooks#afterCreate(Workspace, String) */ public WorkspaceImpl createWorkspace(WorkspaceConfig config, String namespace, - Map attributes, - @Nullable String accountId) throws ServerException, - NotFoundException, - ConflictException { + Map attributes) throws ServerException, + NotFoundException, + ConflictException { requireNonNull(config, "Required non-null config"); requireNonNull(namespace, "Required non-null namespace"); requireNonNull(attributes, "Required non-null attributes"); return normalizeState(doCreateWorkspace(config, - namespace, + accountManager.getByName(namespace), attributes, - false, - accountId)); + false)); } /** @@ -336,7 +320,6 @@ public class WorkspaceManager { * when any server error occurs * @throws NullPointerException * when {@code workspaceId} is null - * @see WorkspaceHooks#afterRemove(String) */ public void removeWorkspace(String workspaceId) throws ConflictException, ServerException { requireNonNull(workspaceId, "Required non-null workspace id"); @@ -344,7 +327,6 @@ public class WorkspaceManager { throw new ConflictException("The workspace '" + workspaceId + "' is currently running and cannot be removed."); } workspaceDao.remove(workspaceId); - hooks.afterRemove(workspaceId); eventService.publish(new WorkspaceRemovedEvent(workspaceId)); LOG.info("Workspace '{}' removed by user '{}'", workspaceId, sessionUserNameOr("undefined")); } @@ -356,9 +338,6 @@ public class WorkspaceManager { * identifier of workspace which should be started * @param envName * name of environment or null, when default environment should be used - * @param accountId - * account which should be used for this runtime workspace or null when - * it should be automatically detected * @param restore * if true workspace will be restored from snapshot if snapshot exists, * otherwise (if snapshot does not exist) workspace will be started from default source. @@ -375,14 +354,12 @@ public class WorkspaceManager { * @throws NullPointerException * when {@code workspaceId} is null * @throws NotFoundException - * when workspace with given {@code workspaceId} doesn't exist, or - * {@link WorkspaceHooks#beforeStart(Workspace, String, String)} throws this exception + * when workspace with given {@code workspaceId} doesn't exist * @throws ServerException * when any other error occurs during workspace start */ public WorkspaceImpl startWorkspace(String workspaceId, @Nullable String envName, - @Nullable String accountId, @Nullable Boolean restore) throws NotFoundException, ServerException, ConflictException { @@ -391,7 +368,7 @@ public class WorkspaceManager { final String restoreAttr = workspace.getAttributes().get(AUTO_RESTORE_FROM_SNAPSHOT); final boolean autoRestore = restoreAttr == null ? defaultAutoRestore : parseBoolean(restoreAttr); final boolean snapshotExists = !getSnapshot(workspaceId).isEmpty(); - return performAsyncStart(workspace, envName, firstNonNull(restore, autoRestore) && snapshotExists, accountId); + return performAsyncStart(workspace, envName, firstNonNull(restore, autoRestore) && snapshotExists); } /** @@ -401,32 +378,26 @@ public class WorkspaceManager { * workspace configuration from which workspace is created and started * @param namespace * workspace name is unique in this namespace - * @param accountId - * account which should be used for this runtime workspace or null when - * it should be automatically detected * @return starting workspace * @throws NullPointerException * when {@code workspaceId} is null * @throws NotFoundException - * when workspace with given {@code workspaceId} doesn't exist, or - * {@link WorkspaceHooks#beforeStart(Workspace, String, String)} throws this exception + * when workspace with given {@code workspaceId} doesn't exist * @throws ServerException * when any other error occurs during workspace start */ public WorkspaceImpl startWorkspace(WorkspaceConfig config, String namespace, - boolean isTemporary, - @Nullable String accountId) throws ServerException, - NotFoundException, - ConflictException { + boolean isTemporary) throws ServerException, + NotFoundException, + ConflictException { requireNonNull(config, "Required non-null configuration"); requireNonNull(namespace, "Required non-null namespace"); final WorkspaceImpl workspace = doCreateWorkspace(config, - namespace, + accountManager.getByName(namespace), emptyMap(), - isTemporary, - accountId); - performAsyncStart(workspace, workspace.getConfig().getDefaultEnv(), false, accountId); + isTemporary); + performAsyncStart(workspace, workspace.getConfig().getDefaultEnv(), false); return normalizeState(workspace); } @@ -537,8 +508,8 @@ public class WorkspaceManager { public List getSnapshot(String workspaceId) throws ServerException, NotFoundException { requireNonNull(workspaceId, "Required non-null workspace id"); // check if workspace exists - final WorkspaceImpl workspace = workspaceDao.get(workspaceId); - return snapshotDao.findSnapshots(workspace.getNamespace(), workspaceId); + workspaceDao.get(workspaceId); + return snapshotDao.findSnapshots(workspaceId); } /** @@ -616,8 +587,7 @@ public class WorkspaceManager { @VisibleForTesting WorkspaceImpl performAsyncStart(WorkspaceImpl workspace, String envName, - boolean recover, - @Nullable String accountId) throws ConflictException, NotFoundException, ServerException { + boolean recover) throws ConflictException, NotFoundException, ServerException { if (envName != null && !workspace.getConfig() .getEnvironments() .containsKey(envName)) { @@ -645,7 +615,6 @@ public class WorkspaceManager { executor.execute(ThreadLocalPropagateContext.wrap(() -> { try { final String env = firstNonNull(envName, workspace.getConfig().getDefaultEnv()); - hooks.beforeStart(workspace, env, accountId); runtimes.start(workspace, env, recover); LOG.info("Workspace '{}:{}' with id '{}' started by user '{}'", workspace.getNamespace(), @@ -833,25 +802,22 @@ public class WorkspaceManager { } private WorkspaceImpl doCreateWorkspace(WorkspaceConfig config, - String namespace, + Account account, Map attributes, - boolean isTemporary, - @Nullable String accountId) throws NotFoundException, - ServerException, - ConflictException { + boolean isTemporary) throws NotFoundException, + ServerException, + ConflictException { final WorkspaceImpl workspace = WorkspaceImpl.builder() .generateId() .setConfig(config) - .setNamespace(namespace) + .setAccount(account) .setAttributes(attributes) .setTemporary(isTemporary) .build(); workspace.getAttributes().put(CREATED_ATTRIBUTE_NAME, Long.toString(currentTimeMillis())); - hooks.beforeCreate(workspace, accountId); workspaceDao.create(workspace); - hooks.afterCreate(workspace, accountId); LOG.info("Workspace '{}:{}' with id '{}' created by user '{}'", - namespace, + account.getName(), workspace.getConfig().getName(), workspace.getId(), sessionUserNameOr("undefined")); @@ -873,19 +839,4 @@ public class WorkspaceManager { final String namespace = nsPart.isEmpty() ? sessionUser().getUserName() : nsPart; return workspaceDao.get(wsName, namespace); } - - /** No-operations workspace hooks. Each method does nothing */ - private static class NoopWorkspaceHooks implements WorkspaceHooks { - @Override - public void beforeStart(Workspace workspace, String evnName, String accountId) throws NotFoundException, ServerException {} - - @Override - public void beforeCreate(Workspace workspace, String accountId) throws NotFoundException, ServerException {} - - @Override - public void afterCreate(Workspace workspace, String accountId) throws ServerException {} - - @Override - public void afterRemove(String workspaceId) {} - } } diff --git a/wsmaster/che-core-api-workspace/src/main/java/org/eclipse/che/api/workspace/server/WorkspaceService.java b/wsmaster/che-core-api-workspace/src/main/java/org/eclipse/che/api/workspace/server/WorkspaceService.java index 71a6a2ed07..8cd4dc94a3 100644 --- a/wsmaster/che-core-api-workspace/src/main/java/org/eclipse/che/api/workspace/server/WorkspaceService.java +++ b/wsmaster/che-core-api-workspace/src/main/java/org/eclipse/che/api/workspace/server/WorkspaceService.java @@ -63,6 +63,7 @@ import static java.util.stream.Collectors.toList; import static javax.ws.rs.core.MediaType.APPLICATION_JSON; import static org.eclipse.che.api.workspace.server.DtoConverter.asDto; import static org.eclipse.che.api.workspace.shared.Constants.LINK_REL_CREATE_WORKSPACE; +import static org.eclipse.che.api.workspace.shared.Constants.LINK_REL_GET_BY_NAMESPACE; import static org.eclipse.che.api.workspace.shared.Constants.LINK_REL_GET_WORKSPACES; /** @@ -75,8 +76,8 @@ import static org.eclipse.che.api.workspace.shared.Constants.LINK_REL_GET_WORKSP @Path("/workspace") public class WorkspaceService extends Service { - private final WorkspaceManager workspaceManager; - private final WorkspaceValidator validator; + private final WorkspaceManager workspaceManager; + private final WorkspaceValidator validator; private final WorkspaceServiceLinksInjector linksInjector; @Context @@ -118,9 +119,9 @@ public class WorkspaceService extends Service { @QueryParam("start-after-create") @DefaultValue("false") Boolean startAfterCreate, - @ApiParam("The account id related to this operation") - @QueryParam("account") - String accountId) throws ConflictException, + @ApiParam("Namespace where workspace should be created") + @QueryParam("namespace") + String namespace) throws ConflictException, ServerException, BadRequestException, ForbiddenException, @@ -129,12 +130,14 @@ public class WorkspaceService extends Service { final Map attributes = parseAttrs(attrsList); validator.validateAttributes(attributes); validator.validateConfig(config); + if (namespace == null) { + namespace = EnvironmentContext.getCurrent().getSubject().getUserName(); + } final WorkspaceImpl workspace = workspaceManager.createWorkspace(config, - EnvironmentContext.getCurrent().getSubject().getUserName(), - attributes, - accountId); + namespace, + attributes); if (startAfterCreate) { - workspaceManager.startWorkspace(workspace.getId(), null, accountId, false); + workspaceManager.startWorkspace(workspace.getId(), null, false); } return Response.status(201) .entity(linksInjector.injectLinks(asDto(workspace), getServiceContext())) @@ -192,6 +195,29 @@ public class WorkspaceService extends Service { .collect(toList()); } + @GET + @Path("/namespace/{namespace}") + @Produces(APPLICATION_JSON) + @GenerateLink(rel = LINK_REL_GET_BY_NAMESPACE) + @ApiOperation(value = "Get workspaces by given namespace", + notes = "This operation can be performed only by authorized user", + response = WorkspaceDto.class, + responseContainer = "List") + @ApiResponses({@ApiResponse(code = 200, message = "The workspaces successfully fetched"), + @ApiResponse(code = 500, message = "Internal server error occurred during workspaces fetching")}) + public List getByNamespace(@ApiParam("Workspace status") + @QueryParam("status") + String status, + @ApiParam("The namespace") + @PathParam("namespace") + String namespace) throws ServerException, BadRequestException { + return workspaceManager.getByNamespace(namespace) + .stream() + .filter(ws -> status == null || status.equalsIgnoreCase(ws.getStatus().toString())) + .map(workspace -> linksInjector.injectLinks(asDto(workspace), getServiceContext())) + .collect(toList()); + } + @PUT @Path("/{id}") @Consumes(APPLICATION_JSON) @@ -254,9 +280,6 @@ public class WorkspaceService extends Service { @ApiParam("The name of the workspace environment that should be used for start") @QueryParam("environment") String envName, - @ApiParam("The account id related to this operation") - @QueryParam("accountId") - String accountId, @ApiParam("Restore workspace from snapshot") @QueryParam("restore") Boolean restore) throws ServerException, @@ -264,8 +287,7 @@ public class WorkspaceService extends Service { NotFoundException, ForbiddenException, ConflictException { - - return linksInjector.injectLinks(asDto(workspaceManager.startWorkspace(workspaceId, envName, accountId, restore)), + return linksInjector.injectLinks(asDto(workspaceManager.startWorkspace(workspaceId, envName, restore)), getServiceContext()); } @@ -289,19 +311,21 @@ public class WorkspaceService extends Service { @ApiParam("Weather this workspace is temporary or not") @QueryParam("temporary") Boolean isTemporary, - @ApiParam("The account id related to this operation") - @QueryParam("account") - String accountId) throws BadRequestException, + @ApiParam("Namespace where workspace should be created") + @QueryParam("namespace") + String namespace) throws BadRequestException, ForbiddenException, NotFoundException, ServerException, ConflictException { requiredNotNull(cfg, "Workspace configuration"); validator.validateConfig(cfg); + if (namespace == null) { + namespace = EnvironmentContext.getCurrent().getSubject().getUserName(); + } return linksInjector.injectLinks(asDto(workspaceManager.startWorkspace(cfg, - EnvironmentContext.getCurrent().getSubject().getUserName(), - firstNonNull(isTemporary, false), - accountId)), getServiceContext()); + namespace, + firstNonNull(isTemporary, false))), getServiceContext()); } @DELETE diff --git a/wsmaster/che-core-api-workspace/src/main/java/org/eclipse/che/api/workspace/server/event/BeforeStackRemovedEvent.java b/wsmaster/che-core-api-workspace/src/main/java/org/eclipse/che/api/workspace/server/event/BeforeStackRemovedEvent.java new file mode 100644 index 0000000000..6db6e1bf6e --- /dev/null +++ b/wsmaster/che-core-api-workspace/src/main/java/org/eclipse/che/api/workspace/server/event/BeforeStackRemovedEvent.java @@ -0,0 +1,31 @@ +/******************************************************************************* + * Copyright (c) 2012-2016 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 + *******************************************************************************/ +package org.eclipse.che.api.workspace.server.event; + +import org.eclipse.che.api.workspace.server.model.impl.stack.StackImpl; + +/** + * Pre-removal event of {@link StackImpl}. + * + * @author Max Shaposhnik + */ +public class BeforeStackRemovedEvent { + + private final StackImpl stack; + + public BeforeStackRemovedEvent(StackImpl stack) { + this.stack = stack; + } + + public StackImpl getStack() { + return stack; + } +} diff --git a/wsmaster/che-core-api-workspace/src/main/java/org/eclipse/che/api/workspace/server/event/BeforeWorkspaceRemovedEvent.java b/wsmaster/che-core-api-workspace/src/main/java/org/eclipse/che/api/workspace/server/event/BeforeWorkspaceRemovedEvent.java new file mode 100644 index 0000000000..aee82833b5 --- /dev/null +++ b/wsmaster/che-core-api-workspace/src/main/java/org/eclipse/che/api/workspace/server/event/BeforeWorkspaceRemovedEvent.java @@ -0,0 +1,31 @@ +/******************************************************************************* + * Copyright (c) 2012-2016 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 + *******************************************************************************/ +package org.eclipse.che.api.workspace.server.event; + +import org.eclipse.che.api.workspace.server.model.impl.WorkspaceImpl; + +/** + * Published before {@link WorkspaceImpl workspace} removed. + * + * @author Yevhenii Voevodin + */ +public class BeforeWorkspaceRemovedEvent { + + private final WorkspaceImpl workspace; + + public BeforeWorkspaceRemovedEvent(WorkspaceImpl workspace) { + this.workspace = workspace; + } + + public WorkspaceImpl getWorkspace() { + return workspace; + } +} diff --git a/wsmaster/che-core-api-workspace/src/main/java/org/eclipse/che/api/workspace/server/jpa/JpaStackDao.java b/wsmaster/che-core-api-workspace/src/main/java/org/eclipse/che/api/workspace/server/jpa/JpaStackDao.java new file mode 100644 index 0000000000..b8d7d07c0b --- /dev/null +++ b/wsmaster/che-core-api-workspace/src/main/java/org/eclipse/che/api/workspace/server/jpa/JpaStackDao.java @@ -0,0 +1,140 @@ +/******************************************************************************* + * Copyright (c) 2012-2016 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 + *******************************************************************************/ +package org.eclipse.che.api.workspace.server.jpa; + +import com.google.inject.persist.Transactional; + +import org.eclipse.che.api.core.ConflictException; +import org.eclipse.che.api.core.NotFoundException; +import org.eclipse.che.api.core.ServerException; +import org.eclipse.che.api.core.jdbc.jpa.DuplicateKeyException; +import org.eclipse.che.api.workspace.server.model.impl.stack.StackImpl; +import org.eclipse.che.api.workspace.server.spi.StackDao; +import org.eclipse.che.commons.annotation.Nullable; + +import javax.inject.Inject; +import javax.inject.Provider; +import javax.inject.Singleton; +import javax.persistence.EntityManager; +import javax.persistence.TypedQuery; +import java.util.List; + +import static java.lang.String.format; +import static java.util.Collections.emptyList; +import static java.util.Objects.requireNonNull; + +/** + * JPA based implementation of {@link StackDao}. + * + * @author Yevhenii Voevodin + */ +@Singleton +public class JpaStackDao implements StackDao { + + @Inject + private Provider managerProvider; + + @Override + public void create(StackImpl stack) throws ConflictException, ServerException { + requireNonNull(stack, "Required non-null stack"); + try { + doCreate(stack); + } catch (DuplicateKeyException x) { + throw new ConflictException(format("Stack with id '%s' or name '%s' already exists", stack.getId(), stack.getName())); + } catch (RuntimeException x) { + throw new ServerException(x.getLocalizedMessage(), x); + } + } + + @Override + @Transactional + public StackImpl getById(String id) throws NotFoundException, ServerException { + requireNonNull(id, "Required non-null id"); + try { + final StackImpl stack = managerProvider.get().find(StackImpl.class, id); + if (stack == null) { + throw new NotFoundException(format("Stack with id '%s' doesn't exist", id)); + } + return stack; + } catch (RuntimeException x) { + throw new ServerException(x.getLocalizedMessage(), x); + } + } + + @Override + public void remove(String id) throws ServerException { + requireNonNull(id, "Required non-null id"); + try { + doRemove(id); + } catch (RuntimeException x) { + throw new ServerException(x.getLocalizedMessage(), x); + } + } + + @Override + public StackImpl update(StackImpl update) throws NotFoundException, ServerException, ConflictException { + requireNonNull(update, "Required non-null update"); + try { + return doUpdate(update); + } catch (DuplicateKeyException x) { + throw new ConflictException(format("Stack with name '%s' already exists", update.getName())); + } catch (RuntimeException x) { + throw new ServerException(x.getLocalizedMessage(), x); + } + } + + @Override + @Transactional + public List searchStacks(@Nullable String user, + @Nullable List tags, + int skipCount, + int maxItems) throws ServerException { + final TypedQuery query; + if (tags == null || tags.isEmpty()) { + query = managerProvider.get().createNamedQuery("Stack.getAll", StackImpl.class); + } else { + query = managerProvider.get() + .createNamedQuery("Stack.getByTags", StackImpl.class) + .setParameter("tags", tags) + .setParameter("tagsSize", tags.size()); + } + try { + return query.setMaxResults(maxItems) + .setFirstResult(skipCount) + .getResultList(); + } catch (RuntimeException x) { + throw new ServerException(x.getLocalizedMessage(), x); + } + } + + @Transactional + protected void doCreate(StackImpl stack) { + managerProvider.get().persist(stack); + } + + @Transactional + protected void doRemove(String id) { + final EntityManager manager = managerProvider.get(); + final StackImpl stack = manager.find(StackImpl.class, id); + if (stack != null) { + manager.remove(stack); + } + } + + @Transactional + protected StackImpl doUpdate(StackImpl update) throws NotFoundException { + final EntityManager manager = managerProvider.get(); + if (manager.find(StackImpl.class, update.getId()) == null) { + throw new NotFoundException(format("Workspace with id '%s' doesn't exist", update.getId())); + } + return manager.merge(update); + } +} diff --git a/wsmaster/che-core-api-workspace/src/main/java/org/eclipse/che/api/workspace/server/jpa/JpaWorkspaceDao.java b/wsmaster/che-core-api-workspace/src/main/java/org/eclipse/che/api/workspace/server/jpa/JpaWorkspaceDao.java new file mode 100644 index 0000000000..fafcb56fd7 --- /dev/null +++ b/wsmaster/che-core-api-workspace/src/main/java/org/eclipse/che/api/workspace/server/jpa/JpaWorkspaceDao.java @@ -0,0 +1,233 @@ +/******************************************************************************* + * Copyright (c) 2012-2016 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 + *******************************************************************************/ +package org.eclipse.che.api.workspace.server.jpa; + +import com.google.inject.persist.Transactional; + +import org.eclipse.che.account.event.BeforeAccountRemovedEvent; +import org.eclipse.che.api.core.ConflictException; +import org.eclipse.che.api.core.NotFoundException; +import org.eclipse.che.api.core.ServerException; +import org.eclipse.che.api.core.jdbc.jpa.DuplicateKeyException; +import org.eclipse.che.api.core.notification.EventService; +import org.eclipse.che.api.core.notification.EventSubscriber; +import org.eclipse.che.api.machine.server.model.impl.SnapshotImpl; +import org.eclipse.che.api.machine.server.spi.SnapshotDao; +import org.eclipse.che.api.workspace.server.event.BeforeWorkspaceRemovedEvent; +import org.eclipse.che.api.workspace.server.model.impl.WorkspaceImpl; +import org.eclipse.che.api.workspace.server.spi.WorkspaceDao; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import javax.annotation.PostConstruct; +import javax.annotation.PreDestroy; +import javax.inject.Inject; +import javax.inject.Provider; +import javax.inject.Singleton; +import javax.persistence.EntityManager; +import javax.persistence.NoResultException; +import java.util.List; + +import static java.lang.String.format; +import static java.util.Objects.requireNonNull; + +/** + * JPA based implementation of {@link WorkspaceDao}. + * + * @author Yevhenii Voevodin + */ +@Singleton +public class JpaWorkspaceDao implements WorkspaceDao { + + private static final Logger LOG = LoggerFactory.getLogger(JpaWorkspaceDao.class); + + @Inject + private Provider manager; + + @Override + public WorkspaceImpl create(WorkspaceImpl workspace) throws ConflictException, ServerException { + requireNonNull(workspace, "Required non-null workspace"); + try { + doCreate(workspace); + } catch (DuplicateKeyException dkEx) { + throw new ConflictException(format("Workspace with id '%s' or name '%s' in namespace '%s' already exists", + workspace.getId(), + workspace.getName(), + workspace.getNamespace())); + } catch (RuntimeException x) { + throw new ServerException(x.getMessage(), x); + } + return workspace; + } + + @Override + public WorkspaceImpl update(WorkspaceImpl update) throws NotFoundException, ConflictException, ServerException { + requireNonNull(update, "Required non-null update"); + try { + return doUpdate(update); + } catch (DuplicateKeyException dkEx) { + throw new ConflictException(format("Workspace with name '%s' in namespace '%s' already exists", + update.getName(), + update.getNamespace())); + } catch (RuntimeException x) { + throw new ServerException(x.getMessage(), x); + } + } + + @Override + public void remove(String id) throws ConflictException, ServerException { + requireNonNull(id, "Required non-null id"); + try { + doRemove(id); + } catch (RuntimeException x) { + throw new ServerException(x.getLocalizedMessage(), x); + } + } + + @Override + @Transactional + public WorkspaceImpl get(String id) throws NotFoundException, ServerException { + requireNonNull(id, "Required non-null id"); + try { + final WorkspaceImpl workspace = manager.get().find(WorkspaceImpl.class, id); + if (workspace == null) { + throw new NotFoundException(format("Workspace with id '%s' doesn't exist", id)); + } + return workspace; + } catch (RuntimeException x) { + throw new ServerException(x.getLocalizedMessage(), x); + } + } + + @Override + @Transactional + public WorkspaceImpl get(String name, String namespace) throws NotFoundException, ServerException { + requireNonNull(name, "Required non-null name"); + requireNonNull(namespace, "Required non-null namespace"); + try { + return manager.get() + .createNamedQuery("Workspace.getByName", WorkspaceImpl.class) + .setParameter("namespace", namespace) + .setParameter("name", name) + .getSingleResult(); + } catch (NoResultException noResEx) { + throw new NotFoundException(format("Workspace with name '%s' in namespace '%s' doesn't exist", + name, + namespace)); + } catch (RuntimeException x) { + throw new ServerException(x.getLocalizedMessage(), x); + } + } + + @Override + @Transactional + public List getByNamespace(String namespace) throws ServerException { + requireNonNull(namespace, "Required non-null namespace"); + try { + return manager.get() + .createNamedQuery("Workspace.getByNamespace", WorkspaceImpl.class) + .setParameter("namespace", namespace) + .getResultList(); + } catch (RuntimeException x) { + throw new ServerException(x.getLocalizedMessage(), x); + } + } + + @Override + @Transactional + public List getWorkspaces(String userId) throws ServerException { + // TODO respect userId when workers become a part of che + try { + return manager.get().createNamedQuery("Workspace.getAll", WorkspaceImpl.class).getResultList(); + } catch (RuntimeException x) { + throw new ServerException(x.getLocalizedMessage(), x); + } + } + + @Transactional + protected void doCreate(WorkspaceImpl workspace) { + manager.get().persist(workspace); + } + + @Transactional + protected void doRemove(String id) { + final WorkspaceImpl workspace = manager.get().find(WorkspaceImpl.class, id); + if (workspace != null) { + manager.get().remove(workspace); + } + } + + @Transactional + protected WorkspaceImpl doUpdate(WorkspaceImpl update) throws NotFoundException { + if (manager.get().find(WorkspaceImpl.class, update.getId()) == null) { + throw new NotFoundException(format("Workspace with id '%s' doesn't exist", update.getId())); + } + return manager.get().merge(update); + } + + @Singleton + public static class RemoveWorkspaceBeforeAccountRemovedEventSubscriber implements EventSubscriber { + @Inject + private EventService eventService; + @Inject + private WorkspaceDao workspaceDao; + + @PostConstruct + public void subscribe() { + eventService.subscribe(this); + } + + @PreDestroy + public void unsubscribe() { + eventService.unsubscribe(this); + } + + @Override + public void onEvent(BeforeAccountRemovedEvent event) { + try { + for (WorkspaceImpl workspace : workspaceDao.getByNamespace(event.getAccount().getName())) { + workspaceDao.remove(workspace.getId()); + } + } catch (Exception x) { + LOG.error(format("Couldn't remove workspaces before account '%s' is removed", event.getAccount().getId()), x); + } + } + } + + @Singleton + public static class RemoveSnapshotsBeforeWorkspaceRemovedEventSubscriber implements EventSubscriber { + @Inject + private EventService eventService; + @Inject + private SnapshotDao snapshotDao; + + @PostConstruct + public void subscribe() { + eventService.subscribe(this); + } + + @PreDestroy + public void unsubscribe() { + eventService.unsubscribe(this); + } + + @Override + public void onEvent(BeforeWorkspaceRemovedEvent event) { + try { + for (SnapshotImpl snapshot : snapshotDao.findSnapshots(event.getWorkspace().getId())) { + snapshotDao.removeSnapshot(snapshot.getId()); + } + } catch (Exception x) { + LOG.error(format("Couldn't remove snapshots before workspace '%s' is removed", event.getWorkspace().getId()), x); + } + } + } +} diff --git a/wsmaster/che-core-api-workspace/src/main/java/org/eclipse/che/api/workspace/server/jpa/StackEntityListener.java b/wsmaster/che-core-api-workspace/src/main/java/org/eclipse/che/api/workspace/server/jpa/StackEntityListener.java new file mode 100644 index 0000000000..7b2459c346 --- /dev/null +++ b/wsmaster/che-core-api-workspace/src/main/java/org/eclipse/che/api/workspace/server/jpa/StackEntityListener.java @@ -0,0 +1,36 @@ +/******************************************************************************* + * Copyright (c) 2012-2016 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 + *******************************************************************************/ +package org.eclipse.che.api.workspace.server.jpa; + +import org.eclipse.che.api.core.notification.EventService; +import org.eclipse.che.api.workspace.server.event.BeforeStackRemovedEvent; +import org.eclipse.che.api.workspace.server.model.impl.stack.StackImpl; + +import javax.inject.Inject; +import javax.inject.Singleton; +import javax.persistence.PreRemove; + +/** + * Entity events listener for {@link StackImpl}. + * + * @author Max Shaposhnik + */ +@Singleton +public class StackEntityListener { + + @Inject + private EventService eventService; + + @PreRemove + private void preRemove(StackImpl stack) { + eventService.publish(new BeforeStackRemovedEvent(stack)); + } +} diff --git a/wsmaster/che-core-api-workspace/src/main/java/org/eclipse/che/api/workspace/server/jpa/WorkspaceEntityListener.java b/wsmaster/che-core-api-workspace/src/main/java/org/eclipse/che/api/workspace/server/jpa/WorkspaceEntityListener.java new file mode 100644 index 0000000000..63ed2164f6 --- /dev/null +++ b/wsmaster/che-core-api-workspace/src/main/java/org/eclipse/che/api/workspace/server/jpa/WorkspaceEntityListener.java @@ -0,0 +1,36 @@ +/******************************************************************************* + * Copyright (c) 2012-2016 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 + *******************************************************************************/ +package org.eclipse.che.api.workspace.server.jpa; + +import org.eclipse.che.api.core.notification.EventService; +import org.eclipse.che.api.workspace.server.event.BeforeWorkspaceRemovedEvent; +import org.eclipse.che.api.workspace.server.model.impl.WorkspaceImpl; + +import javax.inject.Inject; +import javax.inject.Singleton; +import javax.persistence.PreRemove; + +/** + * Callback for {@link WorkspaceImpl workspace} jpa related events. + * + * @author Yevhenii Voevodin + */ +@Singleton +public class WorkspaceEntityListener { + + @Inject + private EventService eventService; + + @PreRemove + private void preRemove(WorkspaceImpl workspace) { + eventService.publish(new BeforeWorkspaceRemovedEvent(workspace)); + } +} diff --git a/wsmaster/che-core-api-workspace/src/main/java/org/eclipse/che/api/workspace/server/jpa/WorkspaceJpaModule.java b/wsmaster/che-core-api-workspace/src/main/java/org/eclipse/che/api/workspace/server/jpa/WorkspaceJpaModule.java new file mode 100644 index 0000000000..84bcb3de54 --- /dev/null +++ b/wsmaster/che-core-api-workspace/src/main/java/org/eclipse/che/api/workspace/server/jpa/WorkspaceJpaModule.java @@ -0,0 +1,32 @@ +/******************************************************************************* + * Copyright (c) 2012-2016 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 + *******************************************************************************/ +package org.eclipse.che.api.workspace.server.jpa; + +import com.google.inject.AbstractModule; + +import org.eclipse.che.api.workspace.server.jpa.JpaWorkspaceDao.RemoveSnapshotsBeforeWorkspaceRemovedEventSubscriber; +import org.eclipse.che.api.workspace.server.jpa.JpaWorkspaceDao.RemoveWorkspaceBeforeAccountRemovedEventSubscriber; +import org.eclipse.che.api.workspace.server.spi.StackDao; +import org.eclipse.che.api.workspace.server.spi.WorkspaceDao; + +/** + * @author Yevhenii Voevodin + */ +public class WorkspaceJpaModule extends AbstractModule { + + @Override + protected void configure() { + bind(StackDao.class).to(JpaStackDao.class); + bind(WorkspaceDao.class).to(JpaWorkspaceDao.class); + bind(RemoveWorkspaceBeforeAccountRemovedEventSubscriber.class).asEagerSingleton(); + bind(RemoveSnapshotsBeforeWorkspaceRemovedEventSubscriber.class).asEagerSingleton(); + } +} diff --git a/wsmaster/che-core-api-workspace/src/main/java/org/eclipse/che/api/workspace/server/model/impl/EnvironmentImpl.java b/wsmaster/che-core-api-workspace/src/main/java/org/eclipse/che/api/workspace/server/model/impl/EnvironmentImpl.java index c345b9a306..917201f1aa 100644 --- a/wsmaster/che-core-api-workspace/src/main/java/org/eclipse/che/api/workspace/server/model/impl/EnvironmentImpl.java +++ b/wsmaster/che-core-api-workspace/src/main/java/org/eclipse/che/api/workspace/server/model/impl/EnvironmentImpl.java @@ -14,6 +14,14 @@ import org.eclipse.che.api.core.model.workspace.Environment; import org.eclipse.che.api.core.model.workspace.EnvironmentRecipe; import org.eclipse.che.api.core.model.workspace.ExtendedMachine; +import javax.persistence.CascadeType; +import javax.persistence.ElementCollection; +import javax.persistence.Embedded; +import javax.persistence.Entity; +import javax.persistence.GeneratedValue; +import javax.persistence.Id; +import javax.persistence.JoinColumn; +import javax.persistence.OneToMany; import java.util.Map; import java.util.Objects; import java.util.stream.Collectors; @@ -23,8 +31,18 @@ import java.util.stream.Collectors; * * @author Yevhenii Voevodin */ +@Entity(name = "Environment") public class EnvironmentImpl implements Environment { - private EnvironmentRecipeImpl recipe; + + @Id + @GeneratedValue + private Long id; + + @Embedded + private EnvironmentRecipeImpl recipe; + + @OneToMany(cascade = CascadeType.ALL, orphanRemoval = true) + @JoinColumn private Map machines; public EnvironmentImpl() {} diff --git a/wsmaster/che-core-api-workspace/src/main/java/org/eclipse/che/api/workspace/server/model/impl/EnvironmentRecipeImpl.java b/wsmaster/che-core-api-workspace/src/main/java/org/eclipse/che/api/workspace/server/model/impl/EnvironmentRecipeImpl.java index d9a5c34fb1..84fe0ec098 100644 --- a/wsmaster/che-core-api-workspace/src/main/java/org/eclipse/che/api/workspace/server/model/impl/EnvironmentRecipeImpl.java +++ b/wsmaster/che-core-api-workspace/src/main/java/org/eclipse/che/api/workspace/server/model/impl/EnvironmentRecipeImpl.java @@ -12,17 +12,31 @@ package org.eclipse.che.api.workspace.server.model.impl; import org.eclipse.che.api.core.model.workspace.EnvironmentRecipe; +import javax.persistence.Basic; +import javax.persistence.Column; +import javax.persistence.Embeddable; import java.util.Objects; /** * @author Alexander Garagatyi */ +@Embeddable public class EnvironmentRecipeImpl implements EnvironmentRecipe { + + @Basic private String type; + + @Basic private String contentType; + + @Column(columnDefinition = "TEXT") private String content; + + @Basic private String location; + public EnvironmentRecipeImpl() {} + public EnvironmentRecipeImpl(String type, String contentType, String content, diff --git a/wsmaster/che-core-api-workspace/src/main/java/org/eclipse/che/api/workspace/server/model/impl/ExtendedMachineImpl.java b/wsmaster/che-core-api-workspace/src/main/java/org/eclipse/che/api/workspace/server/model/impl/ExtendedMachineImpl.java index 8935976756..525b4347eb 100644 --- a/wsmaster/che-core-api-workspace/src/main/java/org/eclipse/che/api/workspace/server/model/impl/ExtendedMachineImpl.java +++ b/wsmaster/che-core-api-workspace/src/main/java/org/eclipse/che/api/workspace/server/model/impl/ExtendedMachineImpl.java @@ -13,6 +13,13 @@ package org.eclipse.che.api.workspace.server.model.impl; import org.eclipse.che.api.core.model.workspace.ExtendedMachine; import org.eclipse.che.api.core.model.workspace.ServerConf2; +import javax.persistence.CascadeType; +import javax.persistence.ElementCollection; +import javax.persistence.Entity; +import javax.persistence.GeneratedValue; +import javax.persistence.Id; +import javax.persistence.JoinColumn; +import javax.persistence.OneToMany; import java.util.ArrayList; import java.util.HashMap; import java.util.List; @@ -23,10 +30,22 @@ import java.util.stream.Collectors; /** * @author Alexander Garagatyi */ +@Entity(name = "ExternalMachine") public class ExtendedMachineImpl implements ExtendedMachine { - private List agents; + + @Id + @GeneratedValue + private Long id; + + @ElementCollection + private List agents; + + @ElementCollection + private Map attributes; + + @OneToMany(cascade = CascadeType.ALL, orphanRemoval = true) + @JoinColumn private Map servers; - private Map attributes; public ExtendedMachineImpl() {} diff --git a/wsmaster/che-core-api-workspace/src/main/java/org/eclipse/che/api/workspace/server/model/impl/ProjectConfigImpl.java b/wsmaster/che-core-api-workspace/src/main/java/org/eclipse/che/api/workspace/server/model/impl/ProjectConfigImpl.java index b76c25dfbd..32c4d194b6 100644 --- a/wsmaster/che-core-api-workspace/src/main/java/org/eclipse/che/api/workspace/server/model/impl/ProjectConfigImpl.java +++ b/wsmaster/che-core-api-workspace/src/main/java/org/eclipse/che/api/workspace/server/model/impl/ProjectConfigImpl.java @@ -14,6 +14,19 @@ package org.eclipse.che.api.workspace.server.model.impl; import org.eclipse.che.api.core.model.project.SourceStorage; import org.eclipse.che.api.core.model.project.ProjectConfig; +import javax.persistence.Basic; +import javax.persistence.CascadeType; +import javax.persistence.Column; +import javax.persistence.ElementCollection; +import javax.persistence.Entity; +import javax.persistence.GeneratedValue; +import javax.persistence.Id; +import javax.persistence.JoinColumn; +import javax.persistence.MapKey; +import javax.persistence.OneToMany; +import javax.persistence.OneToOne; +import javax.persistence.PostLoad; +import javax.persistence.PreUpdate; import java.util.ArrayList; import java.util.HashMap; import java.util.List; @@ -28,19 +41,43 @@ import static java.util.stream.Collectors.toMap; * @author Eugene Voevodin * @author Dmitry Shnurenko */ +@Entity(name = "ProjectConfig") public class ProjectConfigImpl implements ProjectConfig { - private String name; - private String path; - private String description; - private String type; - private List mixins; - private Map> attributes; - private SourceStorageImpl source; - //private String contentRoot; + @Id + @GeneratedValue + private Long id; - public ProjectConfigImpl() { - } + @Column(nullable = false) + private String path; + + @Column + private String name; + + @Basic + private String type; + + @Basic + private String description; + + @OneToOne(cascade = CascadeType.ALL, orphanRemoval = true) + private SourceStorageImpl source; + + @ElementCollection + private List mixins; + + @OneToMany(cascade = CascadeType.ALL, orphanRemoval = true) + @JoinColumn + @MapKey(name = "name") + private Map dbAttributes; + + // TODO consider using List or Map on model level instead + // Mapping delegated to 'dbAttributes' field + // as it is impossible to map nested list directly + @Column(insertable = false, updatable = false) + private Map> attributes; + + public ProjectConfigImpl() {} public ProjectConfigImpl(ProjectConfig projectConfig) { name = projectConfig.getName(); @@ -58,8 +95,6 @@ public class ProjectConfigImpl implements ProjectConfig { if (sourceStorage != null) { source = new SourceStorageImpl(sourceStorage.getType(), sourceStorage.getLocation(), sourceStorage.getParameters()); } - -// contentRoot = projectConfig.getContentRoot(); } @Override @@ -123,19 +158,10 @@ public class ProjectConfigImpl implements ProjectConfig { } @Override - public SourceStorage getSource() { + public SourceStorageImpl getSource() { return source; } -// @Override -// public String getContentRoot() { -// return contentRoot; -// } -// -// public void setContentRoot(String contentRoot) { -// this.contentRoot = contentRoot; -// } - public void setSource(SourceStorageImpl sourceStorage) { this.source = sourceStorage; } @@ -152,7 +178,6 @@ public class ProjectConfigImpl implements ProjectConfig { && getMixins().equals(other.getMixins()) && getAttributes().equals(other.getAttributes()) && Objects.equals(source, other.getSource()); - //&& Objects.equals(contentRoot, other.getContentRoot()); } @Override @@ -165,7 +190,6 @@ public class ProjectConfigImpl implements ProjectConfig { hash = hash * 31 + getMixins().hashCode(); hash = hash * 31 + getAttributes().hashCode(); hash = hash * 31 + Objects.hashCode(source); - //hash = hash * 31 + Objects.hashCode(contentRoot); return hash; } @@ -179,7 +203,73 @@ public class ProjectConfigImpl implements ProjectConfig { ", mixins=" + mixins + ", attributes=" + attributes + ", source=" + source + -// ", contentRoot='" + contentRoot + '\'' + '}'; } + + @PreUpdate + public void syncDbAttributes() { + dbAttributes = getAttributes().entrySet() + .stream() + .collect(toMap(Map.Entry::getKey, e -> new Attribute(e.getKey(), e.getValue()))); + } + + @PostLoad + private void initEntityAttributes() { + attributes = dbAttributes.values() + .stream() + .collect(toMap(attr -> attr.name, attr -> attr.values)); + } + + @Entity(name = "ProjectAttribute") + private static class Attribute { + + @Id + @GeneratedValue + private Long id; + + @Basic + private String name; + + @ElementCollection + private List values; + + public Attribute() {} + + public Attribute(String name, List values) { + this.name = name; + this.values = values; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (!(obj instanceof Attribute)) { + return false; + } + final Attribute that = (Attribute)obj; + return Objects.equals(id, that.id) + && Objects.equals(name, that.name) + && values.equals(that.values); + } + + @Override + public int hashCode() { + int hash = 7; + hash = 31 * hash + Objects.hashCode(id); + hash = 31 * hash + Objects.hashCode(name); + hash = 31 * hash + values.hashCode(); + return hash; + } + + @Override + public String toString() { + return "Attribute{" + + "values=" + values + + ", name='" + name + '\'' + + ", id=" + id + + '}'; + } + } } diff --git a/wsmaster/che-core-api-workspace/src/main/java/org/eclipse/che/api/workspace/server/model/impl/ServerConf2Impl.java b/wsmaster/che-core-api-workspace/src/main/java/org/eclipse/che/api/workspace/server/model/impl/ServerConf2Impl.java index 62b3025d97..dcfce2d7cf 100644 --- a/wsmaster/che-core-api-workspace/src/main/java/org/eclipse/che/api/workspace/server/model/impl/ServerConf2Impl.java +++ b/wsmaster/che-core-api-workspace/src/main/java/org/eclipse/che/api/workspace/server/model/impl/ServerConf2Impl.java @@ -12,6 +12,12 @@ package org.eclipse.che.api.workspace.server.model.impl; import org.eclipse.che.api.core.model.workspace.ServerConf2; +import javax.persistence.Basic; +import javax.persistence.ElementCollection; +import javax.persistence.Embeddable; +import javax.persistence.Entity; +import javax.persistence.GeneratedValue; +import javax.persistence.Id; import java.util.HashMap; import java.util.Map; import java.util.Objects; @@ -19,11 +25,24 @@ import java.util.Objects; /** * @author Alexander Garagatyi */ +@Entity(name = "ServerConf") public class ServerConf2Impl implements ServerConf2 { - private String port; - private String protocol; + + @Id + @GeneratedValue + private Long id; + + @Basic + private String port; + + @Basic + private String protocol; + + @ElementCollection private Map properties; + public ServerConf2Impl() {} + public ServerConf2Impl(String port, String protocol, Map properties) { diff --git a/wsmaster/che-core-api-workspace/src/main/java/org/eclipse/che/api/workspace/server/model/impl/SourceStorageImpl.java b/wsmaster/che-core-api-workspace/src/main/java/org/eclipse/che/api/workspace/server/model/impl/SourceStorageImpl.java index 63c02a8f02..14f77b4efa 100644 --- a/wsmaster/che-core-api-workspace/src/main/java/org/eclipse/che/api/workspace/server/model/impl/SourceStorageImpl.java +++ b/wsmaster/che-core-api-workspace/src/main/java/org/eclipse/che/api/workspace/server/model/impl/SourceStorageImpl.java @@ -12,6 +12,13 @@ package org.eclipse.che.api.workspace.server.model.impl; import org.eclipse.che.api.core.model.project.SourceStorage; +import javax.persistence.Basic; +import javax.persistence.Column; +import javax.persistence.ElementCollection; +import javax.persistence.Entity; +import javax.persistence.GeneratedValue; +import javax.persistence.Id; +import javax.persistence.Lob; import java.util.HashMap; import java.util.Map; import java.util.Objects; @@ -21,12 +28,24 @@ import java.util.Objects; * * @author Yevhenii Voevodin */ +@Entity(name = "SourceStorage") public class SourceStorageImpl implements SourceStorage { - private String type; - private String location; + @Id + @GeneratedValue + private Long id; + + @Basic + private String type; + + @Column(columnDefinition = "TEXT") + private String location; + + @ElementCollection private Map parameters; + public SourceStorageImpl() {} + public SourceStorageImpl(String type, String location, Map parameters) { this.type = type; this.location = location; @@ -40,11 +59,20 @@ public class SourceStorageImpl implements SourceStorage { return type; } + public void setType(String type) { + this.type = type; + } + @Override public String getLocation() { return location; } + + public void setLocation(String location) { + this.location = location; + } + @Override public Map getParameters() { if (parameters == null) { @@ -53,6 +81,10 @@ public class SourceStorageImpl implements SourceStorage { return parameters; } + public void setParameters(Map parameters) { + this.parameters = parameters; + } + @Override public boolean equals(Object o) { if (this == o) return true; diff --git a/wsmaster/che-core-api-workspace/src/main/java/org/eclipse/che/api/workspace/server/model/impl/WorkspaceConfigImpl.java b/wsmaster/che-core-api-workspace/src/main/java/org/eclipse/che/api/workspace/server/model/impl/WorkspaceConfigImpl.java index 40db6b0c22..1724d9c99e 100644 --- a/wsmaster/che-core-api-workspace/src/main/java/org/eclipse/che/api/workspace/server/model/impl/WorkspaceConfigImpl.java +++ b/wsmaster/che-core-api-workspace/src/main/java/org/eclipse/che/api/workspace/server/model/impl/WorkspaceConfigImpl.java @@ -17,7 +17,18 @@ import org.eclipse.che.api.core.model.workspace.WorkspaceConfig; import org.eclipse.che.api.machine.server.model.impl.CommandImpl; import org.eclipse.che.commons.annotation.Nullable; +import javax.persistence.Basic; +import javax.persistence.CascadeType; +import javax.persistence.Column; +import javax.persistence.Entity; +import javax.persistence.GeneratedValue; +import javax.persistence.Id; +import javax.persistence.JoinColumn; +import javax.persistence.OneToMany; +import javax.persistence.PrePersist; +import javax.persistence.PreUpdate; import java.util.ArrayList; +import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Objects; @@ -32,19 +43,40 @@ import static java.util.stream.Collectors.toMap; * @author Alexander Garagatyi * @author Yevhenii Voevodin */ +@Entity(name = "WorkspaceConfig") public class WorkspaceConfigImpl implements WorkspaceConfig { public static WorkspaceConfigImplBuilder builder() { return new WorkspaceConfigImplBuilder(); } - private String name; - private String description; - private String defaultEnv; - private List commands; - private List projects; + @Id + @GeneratedValue + private Long id; + + @Column(nullable = false) + private String name; + + @Basic + private String description; + + @Column(nullable = false) + private String defaultEnv; + + @OneToMany(cascade = CascadeType.ALL, orphanRemoval = true) + @JoinColumn + private List commands; + + @OneToMany(cascade = CascadeType.ALL, orphanRemoval = true) + @JoinColumn + private List projects; + + @OneToMany(cascade = CascadeType.ALL, orphanRemoval = true) + @JoinColumn private Map environments; + public WorkspaceConfigImpl() {} + public WorkspaceConfigImpl(String name, String description, String defaultEnv, @@ -105,6 +137,10 @@ public class WorkspaceConfigImpl implements WorkspaceConfig { return defaultEnv; } + public void setDefaultEnv(String defaultEnv) { + this.defaultEnv = defaultEnv; + } + @Override public List getCommands() { if (commands == null) { @@ -131,9 +167,16 @@ public class WorkspaceConfigImpl implements WorkspaceConfig { @Override public Map getEnvironments() { + if (environments == null) { + return new HashMap<>(); + } return environments; } + public void setEnvironments(Map environments) { + this.environments = environments; + } + @Override public boolean equals(Object obj) { if (this == obj) return true; @@ -171,6 +214,12 @@ public class WorkspaceConfigImpl implements WorkspaceConfig { '}'; } + @PreUpdate + @PrePersist + public void syncProjects() { + getProjects().forEach(ProjectConfigImpl::syncDbAttributes); + } + /** * Helps to build complex {@link WorkspaceConfigImpl users workspace instance}. * diff --git a/wsmaster/che-core-api-workspace/src/main/java/org/eclipse/che/api/workspace/server/model/impl/WorkspaceImpl.java b/wsmaster/che-core-api-workspace/src/main/java/org/eclipse/che/api/workspace/server/model/impl/WorkspaceImpl.java index 98794dd2c2..92c250270b 100644 --- a/wsmaster/che-core-api-workspace/src/main/java/org/eclipse/che/api/workspace/server/model/impl/WorkspaceImpl.java +++ b/wsmaster/che-core-api-workspace/src/main/java/org/eclipse/che/api/workspace/server/model/impl/WorkspaceImpl.java @@ -10,13 +10,35 @@ *******************************************************************************/ package org.eclipse.che.api.workspace.server.model.impl; +import org.eclipse.che.account.shared.model.Account; +import org.eclipse.che.account.spi.AccountImpl; import org.eclipse.che.api.core.model.workspace.Workspace; import org.eclipse.che.api.core.model.workspace.WorkspaceConfig; import org.eclipse.che.api.core.model.workspace.WorkspaceRuntime; import org.eclipse.che.api.core.model.workspace.WorkspaceStatus; +import org.eclipse.che.api.machine.server.model.impl.SnapshotImpl; +import org.eclipse.che.api.workspace.server.jpa.WorkspaceEntityListener; import org.eclipse.che.commons.lang.NameGenerator; +import javax.persistence.Basic; +import javax.persistence.CascadeType; +import javax.persistence.Column; +import javax.persistence.ElementCollection; +import javax.persistence.Entity; +import javax.persistence.EntityListeners; +import javax.persistence.FetchType; +import javax.persistence.Id; +import javax.persistence.JoinColumn; +import javax.persistence.ManyToOne; +import javax.persistence.NamedQueries; +import javax.persistence.NamedQuery; +import javax.persistence.OneToMany; +import javax.persistence.OneToOne; +import javax.persistence.Table; +import javax.persistence.Transient; +import javax.persistence.UniqueConstraint; import java.util.HashMap; +import java.util.List; import java.util.Map; import java.util.Objects; @@ -28,36 +50,80 @@ import static org.eclipse.che.api.core.model.workspace.WorkspaceStatus.STOPPED; * * @author Yevhenii Voevodin */ +@Entity(name = "Workspace") +@Table(uniqueConstraints = @UniqueConstraint(columnNames = {"name", "accountId"})) +@NamedQueries( + { + @NamedQuery(name = "Workspace.getByNamespace", + query = "SELECT w FROM Workspace w WHERE w.account.name = :namespace"), + @NamedQuery(name = "Workspace.getByName", + query = "SELECT w FROM Workspace w WHERE w.account.name = :namespace AND w.name = :name"), + @NamedQuery(name = "Workspace.getAll", + query = "SELECT w FROM Workspace w") + } +) +@EntityListeners(WorkspaceEntityListener.class) public class WorkspaceImpl implements Workspace { public static WorkspaceImplBuilder builder() { return new WorkspaceImplBuilder(); } + @Id private String id; - private String namespace; - private WorkspaceConfigImpl config; - private boolean isTemporary; - private WorkspaceStatus status; - private Map attributes; + @Column(nullable = false) + private String name; + + @OneToOne(cascade = CascadeType.ALL, orphanRemoval = true) + private WorkspaceConfigImpl config; + + @ElementCollection + private Map attributes; + + @Basic + private boolean isTemporary; + + // This mapping is present here just for generation of the constraint between + // snapshots and workspace, it's impossible to do so on snapshot side + // as workspace and machine are different modules and cyclic reference will appear + @OneToMany(fetch = FetchType.LAZY) + @JoinColumn(name = "workspaceId", insertable = false, updatable = false) + private List snapshots; + + @ManyToOne + @JoinColumn(name = "accountId", nullable = false) + private AccountImpl account; + + @Transient + private WorkspaceStatus status = STOPPED; + + @Transient private WorkspaceRuntimeImpl runtime; - public WorkspaceImpl(String id, String namespace, WorkspaceConfig config) { - this(id, namespace, config, null, null, false, STOPPED); + public WorkspaceImpl() {} + + public WorkspaceImpl(String id, Account account, WorkspaceConfig config) { + this(id, account, config.getName(), config, null, null, false, STOPPED); } public WorkspaceImpl(String id, - String namespace, + Account account, + String name, WorkspaceConfig config, WorkspaceRuntime runtime, Map attributes, boolean isTemporary, WorkspaceStatus status) { this.id = id; - this.namespace = namespace; - this.config = new WorkspaceConfigImpl(config); - if (runtime != null) { + this.name = name; + if (account != null) { + this.account = new AccountImpl(account); + } + if (config != null) { + this.config = new WorkspaceConfigImpl(config); + } + if (runtime != null) { this.runtime = new WorkspaceRuntimeImpl(runtime); } if (attributes != null) { @@ -67,16 +133,15 @@ public class WorkspaceImpl implements Workspace { this.isTemporary = isTemporary; } - public WorkspaceImpl(Workspace workspace) { + public WorkspaceImpl(Workspace workspace, Account account) { this(workspace.getId(), - workspace.getNamespace(), - workspace.getConfig()); - this.attributes = new HashMap<>(workspace.getAttributes()); - if (workspace.getRuntime() != null) { - this.runtime = new WorkspaceRuntimeImpl(workspace.getRuntime()); - } - this.isTemporary = workspace.isTemporary(); - this.status = firstNonNull(workspace.getStatus(), STOPPED); + account, + workspace.getConfig().getName(), + workspace.getConfig(), + workspace.getRuntime(), + workspace.getAttributes(), + workspace.isTemporary(), + workspace.getStatus()); } @Override @@ -84,18 +149,37 @@ public class WorkspaceImpl implements Workspace { return id; } + public void setId(String id) { + this.id = id; + } + @Override public String getNamespace() { - return namespace; + if (account != null) { + return account.getName(); + } + return null; + } + + public void setAccount(AccountImpl account) { + this.account = account; + } + + public AccountImpl getAccount() { + return account; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; } @Override - public WorkspaceStatus getStatus() { - return status; - } - - public void setStatus(WorkspaceStatus status) { - this.status = status; + public WorkspaceConfigImpl getConfig() { + return config; } public void setConfig(WorkspaceConfigImpl config) { @@ -119,13 +203,17 @@ public class WorkspaceImpl implements Workspace { return isTemporary; } - public void setTemporary(boolean isTemporary) { - this.isTemporary = isTemporary; + public void setTemporary(boolean temporary) { + isTemporary = temporary; } @Override - public WorkspaceConfigImpl getConfig() { - return config; + public WorkspaceStatus getStatus() { + return status; + } + + public void setStatus(WorkspaceStatus status) { + this.status = status; } @Override @@ -143,7 +231,8 @@ public class WorkspaceImpl implements Workspace { if (!(obj instanceof WorkspaceImpl)) return false; final WorkspaceImpl other = (WorkspaceImpl)obj; return Objects.equals(id, other.id) - && Objects.equals(namespace, other.namespace) + && Objects.equals(getNamespace(), other.getNamespace()) + && Objects.equals(name, other.name) && Objects.equals(status, other.status) && isTemporary == other.isTemporary && getAttributes().equals(other.getAttributes()) @@ -155,7 +244,8 @@ public class WorkspaceImpl implements Workspace { public int hashCode() { int hash = 7; hash = 31 * hash + Objects.hashCode(id); - hash = 31 * hash + Objects.hashCode(namespace); + hash = 31 * hash + Objects.hashCode(getNamespace()); + hash = 31 * hash + Objects.hashCode(name); hash = 31 * hash + Objects.hashCode(status); hash = 31 * hash + Objects.hashCode(config); hash = 31 * hash + getAttributes().hashCode(); @@ -168,7 +258,8 @@ public class WorkspaceImpl implements Workspace { public String toString() { return "WorkspaceImpl{" + "id='" + id + '\'' + - ", namespace='" + namespace + '\'' + + ", namespace='" + getNamespace() + '\'' + + ", name='" + name + '\'' + ", config=" + config + ", isTemporary=" + isTemporary + ", status=" + status + @@ -178,24 +269,25 @@ public class WorkspaceImpl implements Workspace { } /** - * Helps to build complex {@link WorkspaceImpl users workspace instance}. + * Helps to build complex {@link WorkspaceImpl workspace} instance. * * @see WorkspaceImpl#builder() */ public static class WorkspaceImplBuilder { private String id; - private String namespace; + private Account account; + private String name; private boolean isTemporary; private WorkspaceStatus status; - private WorkspaceConfigImpl config; - private WorkspaceRuntimeImpl runtime; + private WorkspaceConfig config; + private WorkspaceRuntime runtime; private Map attributes; private WorkspaceImplBuilder() {} public WorkspaceImpl build() { - return new WorkspaceImpl(id, namespace, config, runtime, attributes, isTemporary, status); + return new WorkspaceImpl(id, account, name, config, runtime, attributes, isTemporary, status); } public WorkspaceImplBuilder generateId() { @@ -204,7 +296,8 @@ public class WorkspaceImpl implements Workspace { } public WorkspaceImplBuilder setConfig(WorkspaceConfig workspaceConfig) { - this.config = new WorkspaceConfigImpl(workspaceConfig); + this.config = workspaceConfig; + this.name = workspaceConfig.getName(); return this; } @@ -213,8 +306,8 @@ public class WorkspaceImpl implements Workspace { return this; } - public WorkspaceImplBuilder setNamespace(String namespace) { - this.namespace = namespace; + public WorkspaceImplBuilder setAccount(Account account) { + this.account = account; return this; } @@ -233,9 +326,14 @@ public class WorkspaceImpl implements Workspace { return this; } - public WorkspaceImplBuilder setRuntime(WorkspaceRuntimeImpl runtime) { + public WorkspaceImplBuilder setRuntime(WorkspaceRuntime runtime) { this.runtime = runtime; return this; } + + public WorkspaceImplBuilder setName(String name) { + this.name = name; + return this; + } } } diff --git a/wsmaster/che-core-api-workspace/src/main/java/org/eclipse/che/api/workspace/server/model/impl/stack/StackComponentImpl.java b/wsmaster/che-core-api-workspace/src/main/java/org/eclipse/che/api/workspace/server/model/impl/stack/StackComponentImpl.java index 7bddfe1e6d..f8fe52a9de 100644 --- a/wsmaster/che-core-api-workspace/src/main/java/org/eclipse/che/api/workspace/server/model/impl/stack/StackComponentImpl.java +++ b/wsmaster/che-core-api-workspace/src/main/java/org/eclipse/che/api/workspace/server/model/impl/stack/StackComponentImpl.java @@ -12,6 +12,8 @@ package org.eclipse.che.api.workspace.server.model.impl.stack; import org.eclipse.che.api.workspace.shared.stack.StackComponent; +import javax.persistence.Basic; +import javax.persistence.Embeddable; import java.util.Objects; /** @@ -19,11 +21,17 @@ import java.util.Objects; * * @author Alexander Andrienko */ +@Embeddable public class StackComponentImpl implements StackComponent { + @Basic private String name; + + @Basic private String version; + public StackComponentImpl() {} + public StackComponentImpl(StackComponent stackComponent) { this(stackComponent.getName(), stackComponent.getVersion()); } diff --git a/wsmaster/che-core-api-workspace/src/main/java/org/eclipse/che/api/workspace/server/model/impl/stack/StackImpl.java b/wsmaster/che-core-api-workspace/src/main/java/org/eclipse/che/api/workspace/server/model/impl/stack/StackImpl.java index d89582ad21..9b20c13456 100644 --- a/wsmaster/che-core-api-workspace/src/main/java/org/eclipse/che/api/workspace/server/model/impl/stack/StackImpl.java +++ b/wsmaster/che-core-api-workspace/src/main/java/org/eclipse/che/api/workspace/server/model/impl/stack/StackImpl.java @@ -10,46 +10,95 @@ *******************************************************************************/ package org.eclipse.che.api.workspace.server.model.impl.stack; -import org.eclipse.che.api.core.acl.AclEntryImpl; import org.eclipse.che.api.core.model.workspace.WorkspaceConfig; +import org.eclipse.che.api.workspace.server.jpa.StackEntityListener; import org.eclipse.che.api.workspace.server.model.impl.WorkspaceConfigImpl; import org.eclipse.che.api.workspace.server.stack.image.StackIcon; import org.eclipse.che.api.workspace.shared.stack.Stack; import org.eclipse.che.api.workspace.shared.stack.StackComponent; import org.eclipse.che.api.workspace.shared.stack.StackSource; -import org.eclipse.che.commons.annotation.Nullable; import org.eclipse.che.commons.lang.NameGenerator; +import javax.persistence.Basic; +import javax.persistence.CascadeType; +import javax.persistence.CollectionTable; +import javax.persistence.Column; +import javax.persistence.ElementCollection; +import javax.persistence.Embedded; +import javax.persistence.Entity; +import javax.persistence.EntityListeners; +import javax.persistence.Id; +import javax.persistence.Index; +import javax.persistence.NamedQueries; +import javax.persistence.NamedQuery; +import javax.persistence.OneToOne; import java.util.ArrayList; import java.util.List; import java.util.Objects; -import static java.util.Objects.requireNonNull; import static java.util.stream.Collectors.toList; /** - * Server implementation of {@link Stack} + * Data object for {@link Stack}. * * @author Alexander Andrienko + * @author Yevhenii Voevodin */ -public class StackImpl implements Stack { +@Entity(name = "Stack") +@NamedQueries( + { + @NamedQuery(name = "Stack.getByTags", + query = "SELECT stack " + + "FROM Stack stack, stack.tags tag " + + "WHERE tag IN :tags " + + "GROUP BY stack.id " + + "HAVING COUNT(tag) = :tagsSize"), + @NamedQuery(name = "Stack.getAll", + query = "SELECT stack FROM Stack stack") + } - private String id; - private String name; - private String description; - private String scope; - private String creator; - private List tags; - private WorkspaceConfigImpl workspaceConfig; - private StackSourceImpl source; - private List components; - private StackIcon stackIcon; - private List acl; +) +@EntityListeners(StackEntityListener.class) +public class StackImpl implements Stack { public static StackBuilder builder() { return new StackBuilder(); } + @Id + private String id; + + @Column(unique = true, nullable = false) + private String name; + + @Basic + private String description; + + @Basic + private String scope; + + @Basic + private String creator; + + @ElementCollection + @Column(name = "tag") + @CollectionTable(indexes = @Index(columnList = "tag")) + private List tags; + + @OneToOne(cascade = CascadeType.ALL, orphanRemoval = true) + private WorkspaceConfigImpl workspaceConfig; + + @Embedded + private StackSourceImpl source; + + @ElementCollection + private List components; + + @Embedded + private StackIcon stackIcon; + + public StackImpl() {} + public StackImpl(StackImpl stack) { this(stack.getId(), stack.getName(), @@ -60,8 +109,7 @@ public class StackImpl implements Stack { stack.getWorkspaceConfig(), stack.getSource(), stack.getComponents(), - stack.getStackIcon(), - stack.getAcl()); + stack.getStackIcon()); } public StackImpl(Stack stack) { @@ -74,41 +122,41 @@ public class StackImpl implements Stack { stack.getWorkspaceConfig(), stack.getSource(), stack.getComponents(), - null, null); } public StackImpl(String id, String name, - @Nullable String description, + String description, String scope, String creator, List tags, - @Nullable WorkspaceConfig workspaceConfig, - @Nullable StackSource source, - @Nullable List components, - @Nullable StackIcon stackIcon, - List acl) { - this.id = requireNonNull(id, "Required non-null stack id"); - this.creator = requireNonNull(creator, "Required non-null stack creator"); - setName(name); - setScope(scope); - setTags(tags); - setWorkspaceConfig(workspaceConfig == null ? null : new WorkspaceConfigImpl(workspaceConfig)); - setSource(source == null ? null : new StackSourceImpl(source)); - - this.stackIcon = stackIcon; + WorkspaceConfig workspaceConfig, + StackSource source, + List components, + StackIcon stackIcon) { + this.id = id; + this.creator = creator; + this.name = name; + this.scope = scope; this.description = description; - - this.components = components == null ? new ArrayList<>() : components.stream() - .map(StackComponentImpl::new) - .collect(toList()); - - if (source == null && workspaceConfig == null) { - throw new IllegalArgumentException("Require non-null source: 'workspaceConfig' or 'stackSource'"); + if (stackIcon != null) { + this.stackIcon = new StackIcon(stackIcon); + } + if (tags != null) { + this.tags = new ArrayList<>(tags); + } + if (workspaceConfig != null) { + this.workspaceConfig = new WorkspaceConfigImpl(workspaceConfig); + } + if (source != null) { + this.source = new StackSourceImpl(source); + } + if (components != null) { + this.components = components.stream() + .map(StackComponentImpl::new) + .collect(toList()); } - - this.acl = acl; } @Override @@ -116,13 +164,16 @@ public class StackImpl implements Stack { return id; } + public void setId(String id) { + this.id = id; + } + @Override public String getName() { return name; } public void setName(String name) { - requireNonNull("require non-null stack name"); this.name = name; } @@ -141,10 +192,6 @@ public class StackImpl implements Stack { } public void setScope(String scope) { - requireNonNull(scope, "Required non-null scope value: 'general' or 'advanced'"); - if (!scope.equals("general") && !scope.equals("advanced")) { - throw new IllegalArgumentException("Stack scope must be 'general' or 'advanced'"); - } this.scope = scope; } @@ -153,6 +200,10 @@ public class StackImpl implements Stack { return creator; } + public void setCreator(String creator) { + this.creator = creator; + } + @Override public List getTags() { if (tags == null) { @@ -162,10 +213,6 @@ public class StackImpl implements Stack { } public void setTags(List tags) { - requireNonNull(tags, "Required non-null stack tags"); - if (tags.isEmpty()) { - throw new IllegalArgumentException("List tags must be non empty"); - } this.tags = tags; } @@ -207,20 +254,6 @@ public class StackImpl implements Stack { this.stackIcon = stackIcon; } - @Nullable - public List getAcl() { - return acl; - } - - public void setAcl(List acl) { - this.acl = acl; - } - - public StackImpl withAcl(List acl) { - this.acl = acl; - return this; - } - @Override public boolean equals(Object obj) { if (this == obj) { @@ -229,18 +262,17 @@ public class StackImpl implements Stack { if (!(obj instanceof StackImpl)) { return false; } - StackImpl other = (StackImpl)obj; - return Objects.equals(id, other.id) && - Objects.equals(name, other.name) && - Objects.equals(description, other.description) && - Objects.equals(creator, other.creator) && - Objects.equals(scope, other.scope) && - getTags().equals(other.getTags()) && - getComponents().equals(other.getComponents()) && - Objects.equals(workspaceConfig, other.workspaceConfig) && - Objects.equals(source, other.source) && - Objects.equals(stackIcon, other.stackIcon) && - Objects.equals(acl, other.getAcl()); + final StackImpl that = (StackImpl)obj; + return Objects.equals(id, that.id) + && Objects.equals(name, that.name) + && Objects.equals(description, that.description) + && Objects.equals(scope, that.scope) + && Objects.equals(creator, that.creator) + && getTags().equals(that.getTags()) + && Objects.equals(workspaceConfig, that.workspaceConfig) + && Objects.equals(source, that.source) + && getComponents().equals(that.getComponents()) + && Objects.equals(stackIcon, that.stackIcon); } @Override @@ -252,28 +284,27 @@ public class StackImpl implements Stack { hash = 31 * hash + Objects.hashCode(scope); hash = 31 * hash + Objects.hashCode(creator); hash = 31 * hash + getTags().hashCode(); - hash = 31 * hash + getComponents().hashCode(); hash = 31 * hash + Objects.hashCode(workspaceConfig); hash = 31 * hash + Objects.hashCode(source); + hash = 31 * hash + getComponents().hashCode(); hash = 31 * hash + Objects.hashCode(stackIcon); - hash = 31 * hash + Objects.hashCode(acl); return hash; } @Override public String toString() { - return "StackImpl{id='" + id + - "', name='" + name + - "', description='" + description + - "', scope='" + scope + - "', creator='" + creator + - "', tags='" + tags + - "', workspaceConfig='" + workspaceConfig + - "', stackSource='" + source + - "', components='" + components + - "', stackIcon='" + stackIcon + - "', acl=" + getAcl() + - "}"; + return "StackImpl{" + + "id='" + id + '\'' + + ", name='" + name + '\'' + + ", description='" + description + '\'' + + ", scope='" + scope + '\'' + + ", creator='" + creator + '\'' + + ", tags=" + tags + + ", workspaceConfig=" + workspaceConfig + + ", source=" + source + + ", components=" + components + + ", stackIcon=" + stackIcon + + '}'; } public static class StackBuilder { @@ -288,7 +319,6 @@ public class StackImpl implements Stack { private StackSource source; private List components; private StackIcon stackIcon; - private List acl; public StackBuilder generateId() { id = NameGenerator.generate("stack", 16); @@ -345,13 +375,17 @@ public class StackImpl implements Stack { return this; } - public StackBuilder setAcl(List acl) { - this.acl = acl; - return this; - } - public StackImpl build() { - return new StackImpl(id, name, description, scope, creator, tags, workspaceConfig, source, components, stackIcon, acl); + return new StackImpl(id, + name, + description, + scope, + creator, + tags, + workspaceConfig, + source, + components, + stackIcon); } } } diff --git a/wsmaster/che-core-api-workspace/src/main/java/org/eclipse/che/api/workspace/server/model/impl/stack/StackSourceImpl.java b/wsmaster/che-core-api-workspace/src/main/java/org/eclipse/che/api/workspace/server/model/impl/stack/StackSourceImpl.java index 3bd7651c66..1f3f55f45b 100644 --- a/wsmaster/che-core-api-workspace/src/main/java/org/eclipse/che/api/workspace/server/model/impl/stack/StackSourceImpl.java +++ b/wsmaster/che-core-api-workspace/src/main/java/org/eclipse/che/api/workspace/server/model/impl/stack/StackSourceImpl.java @@ -13,6 +13,8 @@ package org.eclipse.che.api.workspace.server.model.impl.stack; import org.eclipse.che.api.workspace.shared.stack.StackSource; +import javax.persistence.Basic; +import javax.persistence.Embeddable; import java.util.Objects; /** @@ -20,11 +22,17 @@ import java.util.Objects; * * @author Alexander Andrienko */ +@Embeddable public class StackSourceImpl implements StackSource { + @Basic private String type; + + @Basic private String origin; + public StackSourceImpl() {} + public StackSourceImpl(StackSource stackSource) { this(stackSource.getType(), stackSource.getOrigin()); } @@ -39,11 +47,19 @@ public class StackSourceImpl implements StackSource { return type; } + public void setType(String type) { + this.type = type; + } + @Override public String getOrigin() { return origin; } + public void setOrigin(String origin) { + this.origin = origin; + } + @Override public String toString() { return "StackSourceImpl{" + diff --git a/wsmaster/che-core-api-workspace/src/main/java/org/eclipse/che/api/workspace/server/spi/StackDao.java b/wsmaster/che-core-api-workspace/src/main/java/org/eclipse/che/api/workspace/server/spi/StackDao.java index 42d43e9506..8b411bd7e6 100644 --- a/wsmaster/che-core-api-workspace/src/main/java/org/eclipse/che/api/workspace/server/spi/StackDao.java +++ b/wsmaster/che-core-api-workspace/src/main/java/org/eclipse/che/api/workspace/server/spi/StackDao.java @@ -85,15 +85,27 @@ public interface StackDao { * when {@code update} is null * @throws NotFoundException * when stack with {@code update.getId()} doesn't exist + * @throws ConflictException + * when stack with such name already exists * @throws ServerException * when any error occurs */ - StackImpl update(StackImpl update) throws NotFoundException, ServerException; + StackImpl update(StackImpl update) throws NotFoundException, ConflictException, ServerException; /** - * Searches for stacks which which have read permissions for specified user and contains all of specified {@code tags}. - * Not specified {@code tags} will not take part of search - * Note: only stack which contains permission public: search take part of the search + * Returns those stacks which match the following statements: + *

    + *
  • If neither {@code user} no {@code tags} are specified(null values passed to the method) + * then all the stacks which contain 'search' action in + * {@link StackImpl#getPublicActions() public actions} are returned
  • + *
  • If {@code user} is specified then all the stacks which contain 'search' + * action in stack public actions(like defined by previous list item) + * or those which specify 'search' action in access control entry + * for given {@code user} are returned
  • + *
  • Finally, if {@code tags} are specified then the stacks which match 2 rules above, + * will be filtered by the {@code tags}, stack should contain all of the {@code tags} to be + * in a result list.
  • + *
* * @param user * user id for permission checking @@ -111,5 +123,5 @@ public interface StackDao { * @throws IllegalArgumentException * when {@code skipCount} or {@code maxItems} is negative */ - List searchStacks(String user, @Nullable List tags, int skipCount, int maxItems) throws ServerException; + List searchStacks(@Nullable String user, @Nullable List tags, int skipCount, int maxItems) throws ServerException; } diff --git a/wsmaster/che-core-api-workspace/src/main/java/org/eclipse/che/api/workspace/server/stack/StackLoader.java b/wsmaster/che-core-api-workspace/src/main/java/org/eclipse/che/api/workspace/server/stack/StackLoader.java index 3bdb55ea40..9a4daa0a50 100644 --- a/wsmaster/che-core-api-workspace/src/main/java/org/eclipse/che/api/workspace/server/stack/StackLoader.java +++ b/wsmaster/che-core-api-workspace/src/main/java/org/eclipse/che/api/workspace/server/stack/StackLoader.java @@ -18,6 +18,7 @@ import com.google.inject.Inject; import com.google.inject.Singleton; import com.google.inject.name.Named; +import org.eclipse.che.api.core.ConflictException; import org.eclipse.che.api.core.NotFoundException; import org.eclipse.che.api.core.ServerException; import org.eclipse.che.api.workspace.server.model.impl.stack.StackImpl; @@ -54,6 +55,7 @@ public class StackLoader { private final StackDao stackDao; @Inject + @SuppressWarnings("unused") public StackLoader(@Named("che.stacks.default") String stacksPath, @Named("che.stacks.images.storage") String stackIconFolder, StackDao stackDao) { @@ -81,9 +83,10 @@ public class StackLoader { private void loadStack(StackImpl stack) { setIconData(stack, stackIconFolderPath); + try { stackDao.update(stack); - } catch (NotFoundException | ServerException e) { + } catch (NotFoundException | ConflictException | ServerException e) { try { stackDao.create(stack); } catch (Exception ex) { diff --git a/wsmaster/che-core-api-workspace/src/main/java/org/eclipse/che/api/workspace/server/stack/StackService.java b/wsmaster/che-core-api-workspace/src/main/java/org/eclipse/che/api/workspace/server/stack/StackService.java index a981c581f9..663554dd0b 100644 --- a/wsmaster/che-core-api-workspace/src/main/java/org/eclipse/che/api/workspace/server/stack/StackService.java +++ b/wsmaster/che-core-api-workspace/src/main/java/org/eclipse/che/api/workspace/server/stack/StackService.java @@ -51,7 +51,6 @@ import java.util.Iterator; import java.util.List; import java.util.stream.Collectors; -import static com.google.common.base.Strings.isNullOrEmpty; import static javax.ws.rs.core.MediaType.APPLICATION_JSON; import static javax.ws.rs.core.MediaType.MULTIPART_FORM_DATA; import static javax.ws.rs.core.MediaType.TEXT_PLAIN; @@ -75,11 +74,13 @@ import static org.eclipse.che.api.workspace.shared.Constants.LINK_REL_UPLOAD_ICO @Path("/stack") public class StackService extends Service { - private final StackDao stackDao; + private final StackDao stackDao; + private final StackValidator stackValidator; @Inject - public StackService(StackDao stackDao) { + public StackService(StackDao stackDao, StackValidator stackValidator) { this.stackDao = stackDao; + this.stackValidator = stackValidator; } @POST @@ -96,12 +97,8 @@ public class StackService extends Service { "(e.g. The stack with such name already exists)"), @ApiResponse(code = 500, message = "Internal server error occurred")}) public Response createStack(@ApiParam("The new stack") final StackDto stackDto) throws ApiException { - requireNonNull(stackDto, "Stack required"); - requireNonNullAndNonEmpty(stackDto.getName(), "Stack name required"); - if (stackDto.getSource() == null && stackDto.getWorkspaceConfig() == null) { - throw new BadRequestException("Stack source required. You must specify stack source: 'workspaceConfig' or 'stackSource'"); - } + stackValidator.check(stackDto); String userId = EnvironmentContext.getCurrent().getSubject().getUserId(); StackImpl newStack = StackImpl.builder() @@ -154,11 +151,7 @@ public class StackService extends Service { @ApiParam(value = "The stack id", required = true) @PathParam("id") final String id) throws ApiException { - requireNonNull(updateDto, "Stack required"); - if (updateDto.getSource() == null && updateDto.getWorkspaceConfig() == null) { - throw new BadRequestException("Stack source required. You must specify stack source: 'workspaceConfig' or 'stackSource'"); - } - + stackValidator.check(updateDto); final StackImpl stack = stackDao.getById(id); StackImpl stackForUpdate = StackImpl.builder() @@ -260,7 +253,7 @@ public class StackService extends Service { @ApiParam("The stack id") @PathParam("id") final String id) - throws NotFoundException, ServerException, BadRequestException, ForbiddenException { + throws NotFoundException, ServerException, BadRequestException, ForbiddenException, ConflictException { if (formData.hasNext()) { FileItem fileItem = formData.next(); StackIcon stackIcon = new StackIcon(fileItem.getName(), fileItem.getContentType(), fileItem.get()); @@ -333,16 +326,4 @@ public class StackService extends Service { } return asDto(stack).withLinks(links); } - - private void requireNonNull(Object object, String message) throws BadRequestException { - if (object == null) { - throw new BadRequestException(message); - } - } - - private void requireNonNullAndNonEmpty(String parameter, String message) throws BadRequestException { - if (isNullOrEmpty(parameter)) { - throw new BadRequestException(message); - } - } } diff --git a/wsmaster/che-core-api-workspace/src/main/java/org/eclipse/che/api/workspace/server/stack/StackValidator.java b/wsmaster/che-core-api-workspace/src/main/java/org/eclipse/che/api/workspace/server/stack/StackValidator.java new file mode 100644 index 0000000000..69d923bf41 --- /dev/null +++ b/wsmaster/che-core-api-workspace/src/main/java/org/eclipse/che/api/workspace/server/stack/StackValidator.java @@ -0,0 +1,54 @@ +/******************************************************************************* + * Copyright (c) 2012-2016 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 + *******************************************************************************/ +package org.eclipse.che.api.workspace.server.stack; + +import org.eclipse.che.api.core.BadRequestException; +import org.eclipse.che.api.workspace.shared.stack.Stack; + +import javax.inject.Singleton; + + +/** + * Validator for {@link Stack} objects + * + * @author Mihail Kuznyetsov + */ +@Singleton +public class StackValidator { + + /** + * Validate stack object + * + * @param stack + * stack to validate + * @throws BadRequestException if stack is not valid + */ + public void check(Stack stack) throws BadRequestException { + if (stack == null) { + throw new BadRequestException("Required non-null stack"); + } + if (stack.getCreator() == null) { + throw new BadRequestException("Required non-null stack creator"); + } + if (stack.getName() == null || stack.getName().isEmpty()) { + throw new BadRequestException("Required non-null and non-empty stack name"); + } + if (stack.getScope() == null || !stack.getScope().equals("general") && !stack.getScope().equals("advanced")) { + throw new BadRequestException("Required non-null scope value: 'general' or 'advanced'"); + } + if (stack.getSource() == null && stack.getWorkspaceConfig() == null) { + throw new BadRequestException("Stack source required. You must specify either 'workspaceConfig' or 'stackSource'"); + } + if (stack.getTags() == null || stack.getTags().isEmpty()) { + throw new BadRequestException("Required non-null and non-empty tag list"); + } + } +} diff --git a/wsmaster/che-core-api-workspace/src/main/java/org/eclipse/che/api/workspace/server/stack/image/StackIcon.java b/wsmaster/che-core-api-workspace/src/main/java/org/eclipse/che/api/workspace/server/stack/image/StackIcon.java index ca07d746e0..e88c9d5651 100644 --- a/wsmaster/che-core-api-workspace/src/main/java/org/eclipse/che/api/workspace/server/stack/image/StackIcon.java +++ b/wsmaster/che-core-api-workspace/src/main/java/org/eclipse/che/api/workspace/server/stack/image/StackIcon.java @@ -14,46 +14,38 @@ import com.google.common.base.Objects; import org.eclipse.che.commons.annotation.Nullable; +import javax.persistence.Basic; +import javax.persistence.Column; +import javax.persistence.Embeddable; import java.util.Arrays; -import java.util.Set; - -import static com.google.common.collect.ImmutableSet.of; -import static java.lang.String.format; -import static java.util.Objects.requireNonNull; /** * Class for storing {@link org.eclipse.che.api.workspace.shared.stack.Stack} icon data * * @author Alexander Andrienko */ +@Embeddable public class StackIcon { - private static final Set VALID_MEDIA_TYPES = of("image/jpeg", "image/png", "image/gif", "image/svg+xml"); - private static final int LIMIT_SIZE = 1024 * 1024; - + @Column(name = "icon_name") private String name; + + @Basic private String mediaType; + + @Basic private byte[] data; + public StackIcon() {} + public StackIcon(String name, String mediaType, @Nullable byte[] data) { - if (data != null) { - if (data.length == 0) { - throw new IllegalArgumentException("Incorrect icon data or icon was not attached"); - } - if (data.length > LIMIT_SIZE) { - throw new IllegalArgumentException("Maximum upload size exceeded 1 Mb limit"); - } - } this.data = data; - - requireNonNull(mediaType, "Icon media type required"); - if (!VALID_MEDIA_TYPES.stream().anyMatch(elem -> elem.equals(mediaType))) { - String errorMessage = format("Media type '%s' is unsupported. Supported media types: '%s'", mediaType, VALID_MEDIA_TYPES); - throw new IllegalArgumentException(errorMessage); - } this.mediaType = mediaType; + this.name = name; + } - this.name = requireNonNull(name, "Icon name required"); + public StackIcon(StackIcon icon) { + this(icon.name, icon.mediaType, Arrays.copyOf(icon.data, icon.data.length)); } public String getName() { @@ -90,4 +82,13 @@ public class StackIcon { hash = 31 * hash + Arrays.hashCode(data); return hash; } + + @Override + public String toString() { + return "StackIcon{" + + "name='" + name + '\'' + + ", mediaType='" + mediaType + '\'' + + ", data=[byte array]" + + '}'; + } } diff --git a/wsmaster/che-core-api-workspace/src/test/java/org/eclipse/che/api/environment/server/CheEnvironmentEngineTest.java b/wsmaster/che-core-api-workspace/src/test/java/org/eclipse/che/api/environment/server/CheEnvironmentEngineTest.java index 687c105ef9..38d0e417a7 100644 --- a/wsmaster/che-core-api-workspace/src/test/java/org/eclipse/che/api/environment/server/CheEnvironmentEngineTest.java +++ b/wsmaster/che-core-api-workspace/src/test/java/org/eclipse/che/api/environment/server/CheEnvironmentEngineTest.java @@ -24,7 +24,7 @@ import org.eclipse.che.api.environment.server.compose.ComposeServicesStartStrate import org.eclipse.che.api.environment.server.compose.model.ComposeServiceImpl; import org.eclipse.che.api.environment.server.exception.EnvironmentNotRunningException; import org.eclipse.che.api.machine.server.MachineInstanceProviders; -import org.eclipse.che.api.machine.server.dao.SnapshotDao; +import org.eclipse.che.api.machine.server.spi.SnapshotDao; import org.eclipse.che.api.machine.server.exception.MachineException; import org.eclipse.che.api.machine.server.model.impl.MachineConfigImpl; import org.eclipse.che.api.machine.server.model.impl.MachineImpl; diff --git a/wsmaster/che-core-api-workspace/src/test/java/org/eclipse/che/api/workspace/server/DefaultWorkspaceValidatorTest.java b/wsmaster/che-core-api-workspace/src/test/java/org/eclipse/che/api/workspace/server/DefaultWorkspaceValidatorTest.java index 200243ce90..5eff5d51fd 100644 --- a/wsmaster/che-core-api-workspace/src/test/java/org/eclipse/che/api/workspace/server/DefaultWorkspaceValidatorTest.java +++ b/wsmaster/che-core-api-workspace/src/test/java/org/eclipse/che/api/workspace/server/DefaultWorkspaceValidatorTest.java @@ -10,6 +10,7 @@ *******************************************************************************/ package org.eclipse.che.api.workspace.server; +import org.eclipse.che.account.spi.AccountImpl; import org.eclipse.che.api.core.BadRequestException; import org.eclipse.che.api.environment.server.CheEnvironmentValidator; import org.eclipse.che.api.machine.shared.dto.CommandDto; @@ -118,7 +119,8 @@ public class DefaultWorkspaceValidatorTest { @Test(expectedExceptions = BadRequestException.class, expectedExceptionsMessageRegExp = "Attribute name 'null' is not valid") public void shouldFailValidationIfAttributeNameIsNull() throws Exception { - final WorkspaceImpl workspace = new WorkspaceImpl("id", "namespace", createConfig()); + final AccountImpl account = new AccountImpl("accountId", "namespace", "test"); + final WorkspaceImpl workspace = new WorkspaceImpl("id", account, createConfig()); workspace.getAttributes().put(null, "value1"); @@ -128,7 +130,8 @@ public class DefaultWorkspaceValidatorTest { @Test(expectedExceptions = BadRequestException.class, expectedExceptionsMessageRegExp = "Attribute name '' is not valid") public void shouldFailValidationIfAttributeNameIsEmpty() throws Exception { - final WorkspaceImpl workspace = new WorkspaceImpl("id", "namespace", createConfig()); + final AccountImpl account = new AccountImpl("accountId", "namespace", "test"); + final WorkspaceImpl workspace = new WorkspaceImpl("id", account, createConfig()); workspace.getAttributes().put("", "value1"); wsValidator.validateWorkspace(workspace); @@ -137,7 +140,8 @@ public class DefaultWorkspaceValidatorTest { @Test(expectedExceptions = BadRequestException.class, expectedExceptionsMessageRegExp = "Attribute name '.*' is not valid") public void shouldFailValidationIfAttributeNameStartsWithWordCodenvy() throws Exception { - final WorkspaceImpl workspace = new WorkspaceImpl("id", "namespace", createConfig()); + final AccountImpl account = new AccountImpl("accountId", "namespace", "test"); + final WorkspaceImpl workspace = new WorkspaceImpl("id", account, createConfig()); workspace.getAttributes().put("codenvy_key", "value1"); wsValidator.validateWorkspace(workspace); diff --git a/wsmaster/che-core-api-workspace/src/test/java/org/eclipse/che/api/workspace/server/WorkspaceManagerTest.java b/wsmaster/che-core-api-workspace/src/test/java/org/eclipse/che/api/workspace/server/WorkspaceManagerTest.java index 3d7b5cc7d7..ff3082d77a 100644 --- a/wsmaster/che-core-api-workspace/src/test/java/org/eclipse/che/api/workspace/server/WorkspaceManagerTest.java +++ b/wsmaster/che-core-api-workspace/src/test/java/org/eclipse/che/api/workspace/server/WorkspaceManagerTest.java @@ -10,6 +10,8 @@ *******************************************************************************/ package org.eclipse.che.api.workspace.server; +import org.eclipse.che.account.api.AccountManager; +import org.eclipse.che.account.spi.AccountImpl; import org.eclipse.che.api.core.ConflictException; import org.eclipse.che.api.core.NotFoundException; import org.eclipse.che.api.core.ServerException; @@ -18,7 +20,6 @@ import org.eclipse.che.api.core.model.workspace.WorkspaceConfig; import org.eclipse.che.api.core.model.workspace.WorkspaceStatus; import org.eclipse.che.api.core.notification.EventService; import org.eclipse.che.api.environment.server.MachineProcessManager; -import org.eclipse.che.api.machine.server.dao.SnapshotDao; import org.eclipse.che.api.machine.server.exception.SnapshotException; import org.eclipse.che.api.machine.server.model.impl.MachineConfigImpl; import org.eclipse.che.api.machine.server.model.impl.MachineImpl; @@ -26,6 +27,7 @@ import org.eclipse.che.api.machine.server.model.impl.MachineLimitsImpl; import org.eclipse.che.api.machine.server.model.impl.MachineRuntimeInfoImpl; import org.eclipse.che.api.machine.server.model.impl.MachineSourceImpl; import org.eclipse.che.api.machine.server.model.impl.SnapshotImpl; +import org.eclipse.che.api.machine.server.spi.SnapshotDao; import org.eclipse.che.api.workspace.server.WorkspaceRuntimes.RuntimeDescriptor; import org.eclipse.che.api.workspace.server.model.impl.EnvironmentImpl; import org.eclipse.che.api.workspace.server.model.impl.EnvironmentRecipeImpl; @@ -92,8 +94,9 @@ import static org.testng.Assert.assertTrue; @Listeners(value = {MockitoTestNGListener.class}) public class WorkspaceManagerTest { - private static final String USER_ID = "user123"; - private static final String NAMESPACE = "userNS"; + private static final String USER_ID = "user123"; + private static final String NAMESPACE = "userNS"; + private static final String NAMESPACE_2 = "userNS2"; @Mock private EventService eventService; @@ -104,12 +107,10 @@ public class WorkspaceManagerTest { @Mock private MachineProcessManager client; @Mock - private WorkspaceHooks workspaceHooks; - @Mock - private MachineProcessManager machineProcessManager; - @Mock private WorkspaceRuntimes runtimes; @Mock + private AccountManager accountManager; + @Mock private SnapshotDao snapshotDao; @Captor private ArgumentCaptor workspaceCaptor; @@ -121,11 +122,12 @@ public class WorkspaceManagerTest { workspaceManager = spy(new WorkspaceManager(workspaceDao, runtimes, eventService, + accountManager, false, false, snapshotDao)); - workspaceManager.setHooks(workspaceHooks); - + when(accountManager.getByName(NAMESPACE)).thenReturn(new AccountImpl("accountId", NAMESPACE, "test")); + when(accountManager.getByName(NAMESPACE_2)).thenReturn(new AccountImpl("accountId2", NAMESPACE_2, "test")); when(workspaceDao.create(any(WorkspaceImpl.class))).thenAnswer(invocation -> invocation.getArguments()[0]); when(workspaceDao.update(any(WorkspaceImpl.class))).thenAnswer(invocation -> invocation.getArguments()[0]); @@ -141,24 +143,22 @@ public class WorkspaceManagerTest { public void shouldBeAbleToCreateWorkspace() throws Exception { final WorkspaceConfig cfg = createConfig(); - final WorkspaceImpl workspace = workspaceManager.createWorkspace(cfg, "user123", "account"); + final WorkspaceImpl workspace = workspaceManager.createWorkspace(cfg, NAMESPACE); assertNotNull(workspace); assertFalse(isNullOrEmpty(workspace.getId())); - assertEquals(workspace.getNamespace(), "user123"); + assertEquals(workspace.getNamespace(), NAMESPACE); assertEquals(workspace.getConfig(), cfg); assertFalse(workspace.isTemporary()); assertEquals(workspace.getStatus(), STOPPED); assertNotNull(workspace.getAttributes().get(CREATED_ATTRIBUTE_NAME)); - verify(workspaceHooks).beforeCreate(workspace, "account"); - verify(workspaceHooks).afterCreate(workspace, "account"); verify(workspaceDao).create(workspace); } @Test public void shouldBeAbleToGetWorkspaceById() throws Exception { - final WorkspaceImpl workspace = workspaceManager.createWorkspace(createConfig(), "user123", "account"); + final WorkspaceImpl workspace = workspaceManager.createWorkspace(createConfig(), NAMESPACE); when(workspaceDao.get(workspace.getId())).thenReturn(workspace); when(runtimes.get(any())).thenThrow(new NotFoundException("")); @@ -169,7 +169,7 @@ public class WorkspaceManagerTest { @Test public void getWorkspaceByIdShouldReturnWorkspaceWithStatusEqualToItsRuntimeStatus() throws Exception { - final WorkspaceImpl workspace = workspaceManager.createWorkspace(createConfig(), "user123", "account"); + final WorkspaceImpl workspace = workspaceManager.createWorkspace(createConfig(), NAMESPACE); when(workspaceDao.get(workspace.getId())).thenReturn(workspace); final RuntimeDescriptor descriptor = createDescriptor(workspace, STARTING); when(runtimes.get(workspace.getId())).thenReturn(descriptor); @@ -181,7 +181,7 @@ public class WorkspaceManagerTest { @Test public void shouldBeAbleToGetWorkspaceByName() throws Exception { - final WorkspaceImpl workspace = workspaceManager.createWorkspace(createConfig(), "user123", "account"); + final WorkspaceImpl workspace = workspaceManager.createWorkspace(createConfig(), NAMESPACE); when(workspaceDao.get(workspace.getConfig().getName(), workspace.getNamespace())).thenReturn(workspace); when(runtimes.get(any())).thenThrow(new NotFoundException("")); @@ -199,7 +199,7 @@ public class WorkspaceManagerTest { @Test public void shouldBeAbleToGetWorkspaceByKey() throws Exception { - final WorkspaceImpl workspace = workspaceManager.createWorkspace(createConfig(), NAMESPACE, "account"); + final WorkspaceImpl workspace = workspaceManager.createWorkspace(createConfig(), NAMESPACE); when(workspaceDao.get(workspace.getConfig().getName(), workspace.getNamespace())).thenReturn(workspace); when(runtimes.get(any())).thenThrow(new NotFoundException("")); @@ -209,7 +209,7 @@ public class WorkspaceManagerTest { @Test public void shouldBeAbleToGetWorkspaceByKeyWithoutOwner() throws Exception { - final WorkspaceImpl workspace = workspaceManager.createWorkspace(createConfig(), NAMESPACE, "account"); + final WorkspaceImpl workspace = workspaceManager.createWorkspace(createConfig(), NAMESPACE); when(workspaceDao.get(workspace.getConfig().getName(), workspace.getNamespace())).thenReturn(workspace); when(runtimes.get(any())).thenThrow(new NotFoundException("")); @@ -222,15 +222,15 @@ public class WorkspaceManagerTest { // given final WorkspaceConfig config = createConfig(); - final WorkspaceImpl workspace1 = workspaceManager.createWorkspace(config, "user123", null); - final WorkspaceImpl workspace2 = workspaceManager.createWorkspace(config, "user321", null); + final WorkspaceImpl workspace1 = workspaceManager.createWorkspace(config, NAMESPACE); + final WorkspaceImpl workspace2 = workspaceManager.createWorkspace(config, NAMESPACE_2); - when(workspaceDao.getWorkspaces("user123")).thenReturn(asList(workspace1, workspace2)); + when(workspaceDao.getWorkspaces(NAMESPACE)).thenReturn(asList(workspace1, workspace2)); final RuntimeDescriptor descriptor = createDescriptor(workspace2, RUNNING); when(runtimes.get(workspace2.getId())).thenReturn(descriptor); // when - final List result = workspaceManager.getWorkspaces("user123"); + final List result = workspaceManager.getWorkspaces(NAMESPACE); // then assertEquals(result.size(), 2); @@ -249,14 +249,14 @@ public class WorkspaceManagerTest { // given final WorkspaceConfig config = createConfig(); - final WorkspaceImpl workspace2 = workspaceManager.createWorkspace(config, "user321", null); + final WorkspaceImpl workspace2 = workspaceManager.createWorkspace(config, NAMESPACE_2); - when(workspaceDao.getByNamespace("user321")).thenReturn(singletonList(workspace2)); + when(workspaceDao.getByNamespace(NAMESPACE_2)).thenReturn(singletonList(workspace2)); final RuntimeDescriptor descriptor = createDescriptor(workspace2, RUNNING); when(runtimes.get(workspace2.getId())).thenReturn(descriptor); // when - final List result = workspaceManager.getByNamespace("user321"); + final List result = workspaceManager.getByNamespace(NAMESPACE_2); // then assertEquals(result.size(), 1); @@ -268,7 +268,7 @@ public class WorkspaceManagerTest { @Test public void getWorkspaceByNameShouldReturnWorkspaceWithStatusEqualToItsRuntimeStatus() throws Exception { - final WorkspaceImpl workspace = workspaceManager.createWorkspace(createConfig(), "user123", "account"); + final WorkspaceImpl workspace = workspaceManager.createWorkspace(createConfig(), NAMESPACE); when(workspaceDao.get(workspace.getConfig().getName(), workspace.getNamespace())).thenReturn(workspace); final RuntimeDescriptor descriptor = createDescriptor(workspace, STARTING); when(runtimes.get(any())).thenReturn(descriptor); @@ -280,8 +280,8 @@ public class WorkspaceManagerTest { @Test public void shouldBeAbleToUpdateWorkspace() throws Exception { - final WorkspaceImpl workspace = workspaceManager.createWorkspace(createConfig(), "user123", "account"); - when(workspaceDao.get(workspace.getId())).thenReturn(new WorkspaceImpl(workspace)); + final WorkspaceImpl workspace = workspaceManager.createWorkspace(createConfig(), NAMESPACE); + when(workspaceDao.get(workspace.getId())).thenReturn(new WorkspaceImpl(workspace, workspace.getAccount())); when(runtimes.get(any())).thenThrow(new NotFoundException("")); workspace.setTemporary(true); workspace.getAttributes().put("new attribute", "attribute"); @@ -298,7 +298,7 @@ public class WorkspaceManagerTest { @Test public void workspaceUpdateShouldReturnWorkspaceWithStatusEqualToItsRuntimeStatus() throws Exception { - final WorkspaceImpl workspace = workspaceManager.createWorkspace(createConfig(), "user123", "account"); + final WorkspaceImpl workspace = workspaceManager.createWorkspace(createConfig(), NAMESPACE); when(workspaceDao.get(workspace.getId())).thenReturn(workspace); final RuntimeDescriptor descriptor = createDescriptor(workspace, STARTING); when(runtimes.get(workspace.getId())).thenReturn(descriptor); @@ -310,18 +310,17 @@ public class WorkspaceManagerTest { @Test public void shouldRemoveWorkspace() throws Exception { - final WorkspaceImpl workspace = workspaceManager.createWorkspace(createConfig(), "user123", "account"); + final WorkspaceImpl workspace = workspaceManager.createWorkspace(createConfig(), NAMESPACE); when(workspaceDao.get(workspace.getId())).thenReturn(workspace); workspaceManager.removeWorkspace(workspace.getId()); verify(workspaceDao).remove(workspace.getId()); - verify(workspaceHooks).afterRemove(workspace.getId()); } @Test(expectedExceptions = ConflictException.class) public void shouldNotRemoveWorkspaceIfItIsNotStopped() throws Exception { - final WorkspaceImpl workspace = workspaceManager.createWorkspace(createConfig(), "user123", "account"); + final WorkspaceImpl workspace = workspaceManager.createWorkspace(createConfig(), NAMESPACE); when(runtimes.hasRuntime(workspace.getId())).thenReturn(true); workspaceManager.removeWorkspace(workspace.getId()); @@ -329,23 +328,21 @@ public class WorkspaceManagerTest { @Test public void shouldBeAbleToStartWorkspaceById() throws Exception { - final WorkspaceImpl workspace = workspaceManager.createWorkspace(createConfig(), "user123", "account"); + final WorkspaceImpl workspace = workspaceManager.createWorkspace(createConfig(), NAMESPACE); when(workspaceDao.get(workspace.getId())).thenReturn(workspace); when(runtimes.get(any())).thenThrow(new NotFoundException("")); workspaceManager.startWorkspace(workspace.getId(), workspace.getConfig().getDefaultEnv(), - "account", null); verify(runtimes, timeout(2000)).start(workspace, workspace.getConfig().getDefaultEnv(), false); - verify(workspaceHooks, timeout(2000)).beforeStart(workspace, workspace.getConfig().getDefaultEnv(), "account"); assertNotNull(workspace.getAttributes().get(UPDATED_ATTRIBUTE_NAME)); } @Test public void shouldRecoverWorkspaceWhenRecoverParameterIsNullAndAutoRestoreAttributeIsSetAndSnapshotExists() throws Exception { - final WorkspaceImpl workspace = workspaceManager.createWorkspace(createConfig(), "user123", "account"); + final WorkspaceImpl workspace = workspaceManager.createWorkspace(createConfig(), NAMESPACE); workspace.getAttributes().put(AUTO_RESTORE_FROM_SNAPSHOT, "true"); when(workspaceDao.get(workspace.getId())).thenReturn(workspace); when(runtimes.get(any())).thenThrow(new NotFoundException("")); @@ -354,7 +351,6 @@ public class WorkspaceManagerTest { .setEnvName("env") .setDev(true) .setMachineName("machine1") - .setNamespace(workspace.getNamespace()) .setWorkspaceId(workspace.getId()) .setType("docker") .setMachineSource(new MachineSourceImpl("image")); @@ -363,26 +359,22 @@ public class WorkspaceManagerTest { .setDev(false) .setMachineName("machine2") .build(); - when(snapshotDao.findSnapshots(workspace.getNamespace(), workspace.getId())) + when(snapshotDao.findSnapshots(workspace.getId())) .thenReturn(asList(snapshot1, snapshot2)); workspaceManager.startWorkspace(workspace.getId(), workspace.getConfig().getDefaultEnv(), - "account", null); verify(runtimes, timeout(2000)).start(workspace, workspace.getConfig().getDefaultEnv(), true); - verify(workspaceHooks, timeout(2000)).beforeStart(workspace, - workspace.getConfig().getDefaultEnv(), - "account"); assertNotNull(workspace.getAttributes().get(UPDATED_ATTRIBUTE_NAME)); } @Test public void shouldRecoverWorkspaceWhenRecoverParameterIsTrueAndSnapshotExists() throws Exception { - final WorkspaceImpl workspace = workspaceManager.createWorkspace(createConfig(), "user123", "account"); + final WorkspaceImpl workspace = workspaceManager.createWorkspace(createConfig(), NAMESPACE); when(workspaceDao.get(workspace.getId())).thenReturn(workspace); when(runtimes.get(any())).thenThrow(new NotFoundException("")); SnapshotImpl.SnapshotBuilder snapshotBuilder = SnapshotImpl.builder() @@ -390,7 +382,6 @@ public class WorkspaceManagerTest { .setEnvName("env") .setDev(true) .setMachineName("machine1") - .setNamespace(workspace.getNamespace()) .setWorkspaceId(workspace.getId()) .setType("docker") .setMachineSource(new MachineSourceImpl("image")); @@ -399,89 +390,81 @@ public class WorkspaceManagerTest { .setDev(false) .setMachineName("machine2") .build(); - when(snapshotDao.findSnapshots(workspace.getNamespace(), workspace.getId())) + when(snapshotDao.findSnapshots(workspace.getId())) .thenReturn(asList(snapshot1, snapshot2)); workspaceManager.startWorkspace(workspace.getId(), workspace.getConfig().getDefaultEnv(), - "account", true); verify(runtimes, timeout(2000)).start(workspace, workspace.getConfig().getDefaultEnv(), true); - verify(workspaceHooks, timeout(2000)).beforeStart(workspace, workspace.getConfig().getDefaultEnv(), "account"); assertNotNull(workspace.getAttributes().get(UPDATED_ATTRIBUTE_NAME)); } @Test public void shouldNotRecoverWorkspaceWhenRecoverParameterIsNullAndAutoRestoreAttributesIsSetButSnapshotDoesNotExist() throws Exception { - final WorkspaceImpl workspace = workspaceManager.createWorkspace(createConfig(), "user123", "account"); + final WorkspaceImpl workspace = workspaceManager.createWorkspace(createConfig(), NAMESPACE); workspace.getAttributes().put(AUTO_RESTORE_FROM_SNAPSHOT, "true"); when(workspaceDao.get(workspace.getId())).thenReturn(workspace); when(runtimes.get(any())).thenThrow(new NotFoundException("")); workspaceManager.startWorkspace(workspace.getId(), workspace.getConfig().getDefaultEnv(), - "account", null); verify(runtimes, timeout(2000)).start(workspace, workspace.getConfig().getDefaultEnv(), false); - verify(workspaceHooks, timeout(2000)).beforeStart(workspace, workspace.getConfig().getDefaultEnv(), "account"); assertNotNull(workspace.getAttributes().get(UPDATED_ATTRIBUTE_NAME)); } @Test public void shouldNotRecoverWorkspaceWhenRecoverParameterIsTrueButSnapshotDoesNotExist() throws Exception { - final WorkspaceImpl workspace = workspaceManager.createWorkspace(createConfig(), "user123", "account"); + final WorkspaceImpl workspace = workspaceManager.createWorkspace(createConfig(), NAMESPACE); when(workspaceDao.get(workspace.getId())).thenReturn(workspace); when(runtimes.get(any())).thenThrow(new NotFoundException("")); workspaceManager.startWorkspace(workspace.getId(), workspace.getConfig().getDefaultEnv(), - "account", true); verify(runtimes, timeout(2000)).start(workspace, workspace.getConfig().getDefaultEnv(), false); - verify(workspaceHooks, timeout(2000)).beforeStart(workspace, workspace.getConfig().getDefaultEnv(), "account"); assertNotNull(workspace.getAttributes().get(UPDATED_ATTRIBUTE_NAME)); } @Test public void shouldNotRecoverWorkspaceWhenRecoverParameterIsFalseAndAutoRestoreAttributeIsSetAndSnapshotExists() throws Exception { - final WorkspaceImpl workspace = workspaceManager.createWorkspace(createConfig(), "user123", "account"); + final WorkspaceImpl workspace = workspaceManager.createWorkspace(createConfig(), NAMESPACE); workspace.getAttributes().put(AUTO_RESTORE_FROM_SNAPSHOT, "true"); when(workspaceDao.get(workspace.getId())).thenReturn(workspace); when(runtimes.get(any())).thenThrow(new NotFoundException("")); workspaceManager.startWorkspace(workspace.getId(), workspace.getConfig().getDefaultEnv(), - "account", false); verify(runtimes, timeout(2000)).start(workspace, workspace.getConfig().getDefaultEnv(), false); - verify(workspaceHooks, timeout(2000)).beforeStart(workspace, workspace.getConfig().getDefaultEnv(), "account"); assertNotNull(workspace.getAttributes().get(UPDATED_ATTRIBUTE_NAME)); } @Test(expectedExceptions = ConflictException.class, expectedExceptionsMessageRegExp = "Could not start workspace '.*' because its status is '.*'") public void shouldNotBeAbleToStartWorkspaceIfItIsRunning() throws Exception { - final WorkspaceImpl workspace = workspaceManager.createWorkspace(createConfig(), "user123", "account"); + final WorkspaceImpl workspace = workspaceManager.createWorkspace(createConfig(), NAMESPACE); when(workspaceDao.get(workspace.getId())).thenReturn(workspace); final RuntimeDescriptor descriptor = createDescriptor(workspace, STARTING); when(runtimes.get(workspace.getId())).thenReturn(descriptor); - workspaceManager.startWorkspace(workspace.getId(), null, null, null); + workspaceManager.startWorkspace(workspace.getId(), null, null); } @Test public void workspaceStartShouldUseDefaultEnvIfNullEnvNameProvided() throws Exception { - final WorkspaceImpl workspace = workspaceManager.createWorkspace(createConfig(), "user123", "account"); + final WorkspaceImpl workspace = workspaceManager.createWorkspace(createConfig(), NAMESPACE); when(workspaceDao.get(workspace.getId())).thenReturn(workspace); when(runtimes.get(workspace.getId())).thenThrow(new NotFoundException("")); final RuntimeDescriptor descriptor = createDescriptor(workspace, STARTING); when(runtimes.start(any(), anyString(), anyBoolean())).thenReturn(descriptor); - workspaceManager.startWorkspace(workspace.getId(), null, "account", null); + workspaceManager.startWorkspace(workspace.getId(), null, null); // timeout is needed because this invocation will run in separate thread asynchronously verify(runtimes, timeout(2000)).start(workspace, workspace.getConfig().getDefaultEnv(), false); @@ -492,12 +475,12 @@ public class WorkspaceManagerTest { final WorkspaceConfigImpl config = createConfig(); final EnvironmentImpl nonDefaultEnv = new EnvironmentImpl(null, null); config.getEnvironments().put("non-default-env", nonDefaultEnv); - final WorkspaceImpl workspace = workspaceManager.createWorkspace(config, "user123", "account"); + final WorkspaceImpl workspace = workspaceManager.createWorkspace(config, NAMESPACE); when(workspaceDao.get(workspace.getId())).thenReturn(workspace); when(runtimes.get(workspace.getId())).thenThrow(new NotFoundException("")); final RuntimeDescriptor descriptor = createDescriptor(workspace, STARTING); when(runtimes.start(any(), anyString(), anyBoolean())).thenReturn(descriptor); - workspaceManager.startWorkspace(workspace.getId(), "non-default-env", "account", null); + workspaceManager.startWorkspace(workspace.getId(), "non-default-env", false); // timeout is needed because this invocation will run in separate thread asynchronously verify(runtimes, timeout(2000)).start(workspace, "non-default-env", false); @@ -506,13 +489,13 @@ public class WorkspaceManagerTest { @Test(expectedExceptions = NotFoundException.class, expectedExceptionsMessageRegExp = "Workspace '.*' doesn't contain environment '.*'") public void startShouldThrowNotFoundExceptionWhenProvidedEnvDoesNotExist() throws Exception { - final WorkspaceImpl workspace = workspaceManager.createWorkspace(createConfig(), "user123", "account"); + final WorkspaceImpl workspace = workspaceManager.createWorkspace(createConfig(), NAMESPACE); when(workspaceDao.get(workspace.getId())).thenReturn(workspace); when(runtimes.get(workspace.getId())).thenThrow(new NotFoundException("")); final RuntimeDescriptor descriptor = createDescriptor(workspace, STARTING); when(runtimes.start(any(), anyString(), anyBoolean())).thenReturn(descriptor); - workspaceManager.startWorkspace(workspace.getId(), "fake", "account", null); + workspaceManager.startWorkspace(workspace.getId(), "fake", null); } @Test @@ -521,21 +504,17 @@ public class WorkspaceManagerTest { when(runtimes.start(any(), anyString(), anyBoolean())).thenReturn(mock(RuntimeDescriptor.class)); when(runtimes.get(any())).thenThrow(new NotFoundException("")); - final WorkspaceImpl runtime = workspaceManager.startWorkspace(createConfig(), "user123", true, "account"); + final WorkspaceImpl runtime = workspaceManager.startWorkspace(createConfig(), NAMESPACE, true); verify(runtimes, timeout(2000)).start(workspaceCaptor.capture(), anyString(), anyBoolean()); final WorkspaceImpl captured = workspaceCaptor.getValue(); assertTrue(captured.isTemporary()); - verify(workspaceHooks).beforeCreate(captured, "account"); - verify(workspaceHooks).afterCreate(runtime, "account"); - verify(workspaceHooks).beforeStart(captured, config.getDefaultEnv(), "account"); - verify(workspaceManager).performAsyncStart(captured, captured.getConfig().getDefaultEnv(), false, "account"); + verify(workspaceManager).performAsyncStart(captured, captured.getConfig().getDefaultEnv(), false); } @Test public void shouldBeAbleToStopWorkspace() throws Exception { - // given - final WorkspaceImpl workspace = workspaceManager.createWorkspace(createConfig(), "user123", "account"); + final WorkspaceImpl workspace = workspaceManager.createWorkspace(createConfig(), NAMESPACE); final RuntimeDescriptor descriptor = createDescriptor(workspace, RUNNING); when(runtimes.get(any())).thenReturn(descriptor); when(workspaceDao.get(workspace.getId())).thenReturn(workspace); @@ -550,7 +529,7 @@ public class WorkspaceManagerTest { @Test public void shouldCreateWorkspaceSnapshotBeforeStoppingWorkspace() throws Exception { - final WorkspaceImpl workspace = workspaceManager.createWorkspace(createConfig(), "user123", "account"); + final WorkspaceImpl workspace = workspaceManager.createWorkspace(createConfig(), NAMESPACE); workspace.getAttributes().put(Constants.AUTO_CREATE_SNAPSHOT, "true"); when(workspaceDao.get(workspace.getId())).thenReturn(workspace); final RuntimeDescriptor descriptor = createDescriptor(workspace, RUNNING); @@ -564,14 +543,14 @@ public class WorkspaceManagerTest { workspaceManager.stopWorkspace(workspace.getId()); verify(workspaceManager, timeout(2000)).createSnapshotSync(anyObject(), anyString(), anyString()); - verify(runtimes, timeout(2000)).stop(any()); + verify(runtimes, timeout(2000)).stop(workspace.getId()); } @Test(expectedExceptions = ConflictException.class, expectedExceptionsMessageRegExp = "Could not stop the workspace " + "'.*' because its status is 'STARTING'.") public void shouldFailCreatingSnapshotWhenStoppingWorkspaceWhichIsNotRunning() throws Exception { - final WorkspaceImpl workspace = workspaceManager.createWorkspace(createConfig(), "user123", "account"); + final WorkspaceImpl workspace = workspaceManager.createWorkspace(createConfig(), NAMESPACE); workspace.getAttributes().put(Constants.AUTO_CREATE_SNAPSHOT, "true"); final RuntimeDescriptor descriptor = createDescriptor(workspace, STARTING); when(workspaceDao.get(workspace.getId())).thenReturn(workspace); @@ -582,7 +561,7 @@ public class WorkspaceManagerTest { @Test public void shouldStopWorkspaceEventIfSnapshotCreationFailed() throws Exception { - final WorkspaceImpl workspace = workspaceManager.createWorkspace(createConfig(), "user123", "account"); + final WorkspaceImpl workspace = workspaceManager.createWorkspace(createConfig(), NAMESPACE); when(workspaceDao.get(workspace.getId())).thenReturn(workspace); final RuntimeDescriptor descriptor = createDescriptor(workspace, RUNNING); when(runtimes.get(any())).thenReturn(descriptor); @@ -594,7 +573,7 @@ public class WorkspaceManagerTest { @Test public void shouldRemoveTemporaryWorkspaceAfterStop() throws Exception { - final WorkspaceImpl workspace = workspaceManager.createWorkspace(createConfig(), "user123", "account"); + final WorkspaceImpl workspace = workspaceManager.createWorkspace(createConfig(), NAMESPACE); workspace.setTemporary(true); when(workspaceDao.get(workspace.getId())).thenReturn(workspace); final RuntimeDescriptor descriptor = createDescriptor(workspace, RUNNING); @@ -608,27 +587,33 @@ public class WorkspaceManagerTest { @Test public void shouldRemoveTemporaryWorkspaceAfterStartFailed() throws Exception { - final WorkspaceImpl workspace = workspaceManager.createWorkspace(createConfig(), "user123", "account"); + final WorkspaceImpl workspace = workspaceManager.createWorkspace(createConfig(), NAMESPACE); workspace.setTemporary(true); when(workspaceDao.get(workspace.getId())).thenReturn(workspace); when(runtimes.get(any())).thenThrow(new NotFoundException("")); when(runtimes.start(any(), anyString(), anyBoolean())).thenThrow(new ConflictException("")); - workspaceManager.startWorkspace(workspace.getId(), null, null, null); + workspaceManager.startWorkspace(workspace.getId(), null, null); verify(workspaceDao, timeout(2000)).remove(workspace.getId()); } @Test public void shouldBeAbleToGetSnapshots() throws Exception { - final String wsId = "workspace123"; - final WorkspaceImpl workspace = workspaceManager.createWorkspace(createConfig(), NAMESPACE, "account"); - when(workspaceDao.get(wsId)).thenReturn(workspace); - when(snapshotDao.findSnapshots(NAMESPACE, wsId)).thenReturn(singletonList(any())); + final WorkspaceImpl workspace = workspaceManager.createWorkspace(createConfig(), NAMESPACE); + when(workspaceDao.get(workspace.getId())).thenReturn(workspace); + final SnapshotImpl wsSnapshot = SnapshotImpl.builder() + .setDev(true) + .setEnvName("envName") + .setId("snap1") + .setMachineName("machine1") + .setWorkspaceId(workspace.getId()).build(); + when(snapshotDao.findSnapshots(workspace.getId())).thenReturn(singletonList(wsSnapshot)); - final List snapshots = workspaceManager.getSnapshot("workspace123"); + final List snapshots = workspaceManager.getSnapshot(workspace.getId()); assertEquals(snapshots.size(), 1); + assertEquals(snapshots.get(0), wsSnapshot); } @Test @@ -637,10 +622,11 @@ public class WorkspaceManagerTest { workspaceManager = spy(new WorkspaceManager(workspaceDao, runtimes, eventService, + accountManager, true, false, snapshotDao)); - final WorkspaceImpl workspace = workspaceManager.createWorkspace(createConfig(), "user123", "account"); + final WorkspaceImpl workspace = workspaceManager.createWorkspace(createConfig(), NAMESPACE); when(workspaceDao.get(workspace.getId())).thenReturn(workspace); final RuntimeDescriptor descriptor = createDescriptor(workspace, RUNNING); when(runtimes.get(any())).thenReturn(descriptor); @@ -663,10 +649,11 @@ public class WorkspaceManagerTest { workspaceManager = spy(new WorkspaceManager(workspaceDao, runtimes, eventService, + accountManager, false, true, snapshotDao)); - final WorkspaceImpl workspace = workspaceManager.createWorkspace(createConfig(), "user123", "account"); + final WorkspaceImpl workspace = workspaceManager.createWorkspace(createConfig(), NAMESPACE); when(workspaceDao.get(workspace.getId())).thenReturn(workspace); when(runtimes.get(any())).thenThrow(new NotFoundException("")); SnapshotImpl.SnapshotBuilder snapshotBuilder = SnapshotImpl.builder() @@ -674,7 +661,6 @@ public class WorkspaceManagerTest { .setEnvName("env") .setDev(true) .setMachineName("machine1") - .setNamespace(workspace.getNamespace()) .setWorkspaceId(workspace.getId()) .setType("docker") .setMachineSource(new MachineSourceImpl("image")); @@ -683,10 +669,10 @@ public class WorkspaceManagerTest { .setDev(false) .setMachineName("machine2") .build(); - when(snapshotDao.findSnapshots(workspace.getNamespace(), workspace.getId())) + when(snapshotDao.findSnapshots(workspace.getId())) .thenReturn(asList(snapshot1, snapshot2)); - workspaceManager.startWorkspace(workspace.getId(), workspace.getConfig().getDefaultEnv(), "account", null); + workspaceManager.startWorkspace(workspace.getId(), workspace.getConfig().getDefaultEnv(), null); verify(runtimes, timeout(2000)).start(workspace, workspace.getConfig().getDefaultEnv(), true); } @@ -704,7 +690,6 @@ public class WorkspaceManagerTest { .setEnvName("env") .setDev(true) .setMachineName("machine1") - .setNamespace(testNamespace) .setWorkspaceId(testWsId) .setType("docker") .setMachineSource(new MachineSourceImpl("image")); @@ -713,7 +698,7 @@ public class WorkspaceManagerTest { .setDev(false) .setMachineName("machine2") .build(); - when(snapshotDao.findSnapshots(testNamespace, testWsId)).thenReturn(asList(snapshot1, snapshot2)); + when(snapshotDao.findSnapshots(testWsId)).thenReturn(asList(snapshot1, snapshot2)); // when workspaceManager.removeSnapshots(testWsId); @@ -740,7 +725,6 @@ public class WorkspaceManagerTest { .setEnvName("env") .setDev(true) .setMachineName("machine1") - .setNamespace(testNamespace) .setWorkspaceId(testWsId) .setType("docker") .setMachineSource(new MachineSourceImpl("image")); @@ -749,7 +733,7 @@ public class WorkspaceManagerTest { .setDev(false) .setMachineName("machine2") .build(); - when(snapshotDao.findSnapshots(testNamespace, testWsId)).thenReturn(asList(snapshot1, snapshot2)); + when(snapshotDao.findSnapshots(testWsId)).thenReturn(asList(snapshot1, snapshot2)); doThrow(new SnapshotException("test")).when(snapshotDao).removeSnapshot(snapshot1.getId()); // when @@ -797,8 +781,8 @@ public class WorkspaceManagerTest { @Test public void shouldBeAbleToCreateSnapshot() throws Exception { - // given - final WorkspaceImpl workspace = workspaceManager.createWorkspace(createConfig(), "user123", "account"); + // then + final WorkspaceImpl workspace = workspaceManager.createWorkspace(createConfig(), NAMESPACE); when(workspaceDao.get(workspace.getId())).thenReturn(workspace); RuntimeDescriptor descriptor = createDescriptor(workspace, RUNNING); when(runtimes.get(any())).thenReturn(descriptor); @@ -813,15 +797,15 @@ public class WorkspaceManagerTest { // then verify(workspaceManager, timeout(1_000)).createSnapshotSync(any(WorkspaceRuntimeImpl.class), - eq(workspace.getNamespace()), - eq(workspace.getId())); + eq(workspace.getNamespace()), + eq(workspace.getId())); } @Test(expectedExceptions = ConflictException.class, expectedExceptionsMessageRegExp = "Could not .* the workspace '.*' because its status is '.*'.") public void shouldNotCreateSnapshotIfWorkspaceIsNotRunning() throws Exception { - // given - final WorkspaceImpl workspace = workspaceManager.createWorkspace(createConfig(), "user123", "account"); + // then + final WorkspaceImpl workspace = workspaceManager.createWorkspace(createConfig(), NAMESPACE); when(workspaceDao.get(workspace.getId())).thenReturn(workspace); RuntimeDescriptor descriptor = createDescriptor(workspace, STARTING); when(runtimes.get(any())).thenReturn(descriptor); @@ -833,7 +817,7 @@ public class WorkspaceManagerTest { @Test public void shouldSnapshotAllMachinesInWs() throws Exception { // given - final WorkspaceImpl workspace = workspaceManager.createWorkspace(createConfig(), "user123", "account"); + final WorkspaceImpl workspace = workspaceManager.createWorkspace(createConfig(), NAMESPACE); when(workspaceDao.get(workspace.getId())).thenReturn(workspace); RuntimeDescriptor descriptor = createDescriptor(workspace, RUNNING); when(runtimes.get(any())).thenReturn(descriptor); @@ -856,7 +840,7 @@ public class WorkspaceManagerTest { @Test public void shouldSendEventOnStartSnapshotSaving() throws Exception { // given - final WorkspaceImpl workspace = workspaceManager.createWorkspace(createConfig(), "user123", "account"); + final WorkspaceImpl workspace = workspaceManager.createWorkspace(createConfig(), NAMESPACE); when(workspaceDao.get(workspace.getId())).thenReturn(workspace); RuntimeDescriptor descriptor = createDescriptor(workspace, RUNNING); when(runtimes.get(any())).thenReturn(descriptor); @@ -878,7 +862,7 @@ public class WorkspaceManagerTest { @Test public void shouldSendEventOnSuccessfulSnapshotSaving() throws Exception { // given - final WorkspaceImpl workspace = workspaceManager.createWorkspace(createConfig(), "user123", "account"); + final WorkspaceImpl workspace = workspaceManager.createWorkspace(createConfig(), NAMESPACE); when(workspaceDao.get(workspace.getId())).thenReturn(workspace); RuntimeDescriptor descriptor = createDescriptor(workspace, RUNNING); when(runtimes.get(any())).thenReturn(descriptor); @@ -900,7 +884,7 @@ public class WorkspaceManagerTest { @Test public void shouldSendSnapshotSavingFailedEventIfDevMachineSnapshotSavingFailed() throws Exception { // given - final WorkspaceImpl workspace = workspaceManager.createWorkspace(createConfig(), "user123", "account"); + final WorkspaceImpl workspace = workspaceManager.createWorkspace(createConfig(), NAMESPACE); when(workspaceDao.get(workspace.getId())).thenReturn(workspace); RuntimeDescriptor descriptor = createDescriptor(workspace, RUNNING); when(runtimes.get(any())).thenReturn(descriptor); @@ -929,7 +913,7 @@ public class WorkspaceManagerTest { @Test public void shouldNotSendSnapshotSavingFailedEventIfNonDevMachineSnapshotSavingFailed() throws Exception { // given - final WorkspaceImpl workspace = workspaceManager.createWorkspace(createConfig(), "user123", "account"); + final WorkspaceImpl workspace = workspaceManager.createWorkspace(createConfig(), NAMESPACE); when(workspaceDao.get(workspace.getId())).thenReturn(workspace); RuntimeDescriptor descriptor = createDescriptor(workspace, RUNNING); when(runtimes.get(any())).thenReturn(descriptor); @@ -957,7 +941,7 @@ public class WorkspaceManagerTest { @Test public void shouldReturnFalseOnFailureSnapshotSavingIfDevMachineSavingFailed() throws Exception { // given - final WorkspaceImpl workspace = workspaceManager.createWorkspace(createConfig(), "user123", "account"); + final WorkspaceImpl workspace = workspaceManager.createWorkspace(createConfig(), NAMESPACE); when(workspaceDao.get(workspace.getId())).thenReturn(workspace); RuntimeDescriptor descriptor = createDescriptor(workspace, RUNNING); when(runtimes.get(any())).thenReturn(descriptor); @@ -984,7 +968,7 @@ public class WorkspaceManagerTest { @Test public void shouldReturnTrueOnSuccessfulSavingSnapshotsForSeveralMachines() throws Exception { // given - final WorkspaceImpl workspace = workspaceManager.createWorkspace(createConfig(), "user123", "account"); + final WorkspaceImpl workspace = workspaceManager.createWorkspace(createConfig(), NAMESPACE); when(workspaceDao.get(workspace.getId())).thenReturn(workspace); RuntimeDescriptor descriptor = createDescriptor(workspace, RUNNING); when(runtimes.get(any())).thenReturn(descriptor); @@ -1007,7 +991,7 @@ public class WorkspaceManagerTest { @Test public void shouldReturnTrueOnSavingSnapshotsForSeveralMachinesWhenNonDevMachineSavingFailed() throws Exception { // given - final WorkspaceImpl workspace = workspaceManager.createWorkspace(createConfig(), "user123", "account"); + final WorkspaceImpl workspace = workspaceManager.createWorkspace(createConfig(), NAMESPACE); when(workspaceDao.get(workspace.getId())).thenReturn(workspace); RuntimeDescriptor descriptor = createDescriptor(workspace, RUNNING); when(runtimes.get(any())).thenReturn(descriptor); @@ -1034,7 +1018,7 @@ public class WorkspaceManagerTest { @Test public void shouldRemoveRuntimeSnapshotIfSavingSnapshotInDaoFails() throws Exception { // given - final WorkspaceImpl workspace = workspaceManager.createWorkspace(createConfig(), "user123", "account"); + final WorkspaceImpl workspace = workspaceManager.createWorkspace(createConfig(), NAMESPACE); when(workspaceDao.get(workspace.getId())).thenReturn(workspace); RuntimeDescriptor descriptor = createDescriptor(workspace, RUNNING); when(runtimes.get(any())).thenReturn(descriptor); @@ -1055,7 +1039,7 @@ public class WorkspaceManagerTest { @Test public void shouldIgnoreNotFoundExceptionOnOldSnapshotRemoval1() throws Exception { // given - final WorkspaceImpl workspace = workspaceManager.createWorkspace(createConfig(), "user123", "account"); + final WorkspaceImpl workspace = workspaceManager.createWorkspace(createConfig(), NAMESPACE); when(workspaceDao.get(workspace.getId())).thenReturn(workspace); RuntimeDescriptor descriptor = createDescriptor(workspace, RUNNING); when(runtimes.get(any())).thenReturn(descriptor); @@ -1078,7 +1062,7 @@ public class WorkspaceManagerTest { @Test public void shouldIgnoreNotFoundExceptionOnOldSnapshotRemoval2() throws Exception { // given - final WorkspaceImpl workspace = workspaceManager.createWorkspace(createConfig(), "user123", "account"); + final WorkspaceImpl workspace = workspaceManager.createWorkspace(createConfig(), NAMESPACE); when(workspaceDao.get(workspace.getId())).thenReturn(workspace); RuntimeDescriptor descriptor = createDescriptor(workspace, RUNNING); when(runtimes.get(any())).thenReturn(descriptor); @@ -1101,7 +1085,7 @@ public class WorkspaceManagerTest { @Test public void shouldIgnoreNotFoundExceptionOnOldSnapshotRemoval3() throws Exception { // given - final WorkspaceImpl workspace = workspaceManager.createWorkspace(createConfig(), "user123", "account"); + final WorkspaceImpl workspace = workspaceManager.createWorkspace(createConfig(), NAMESPACE); when(workspaceDao.get(workspace.getId())).thenReturn(workspace); RuntimeDescriptor descriptor = createDescriptor(workspace, RUNNING); when(runtimes.get(any())).thenReturn(descriptor); @@ -1122,7 +1106,7 @@ public class WorkspaceManagerTest { @Test public void shouldBeAbleToStopMachine() throws Exception { // given - final WorkspaceImpl workspace = workspaceManager.createWorkspace(createConfig(), "user123", "account"); + final WorkspaceImpl workspace = workspaceManager.createWorkspace(createConfig(), NAMESPACE); when(workspaceDao.get(workspace.getId())).thenReturn(workspace); RuntimeDescriptor descriptor = createDescriptor(workspace, RUNNING); when(runtimes.get(any())).thenReturn(descriptor); @@ -1139,7 +1123,7 @@ public class WorkspaceManagerTest { expectedExceptionsMessageRegExp = "Could not .* the workspace '.*' because its status is '.*'.") public void shouldNotStopMachineIfWorkspaceIsNotRunning() throws Exception { // given - final WorkspaceImpl workspace = workspaceManager.createWorkspace(createConfig(), "user123", "account"); + final WorkspaceImpl workspace = workspaceManager.createWorkspace(createConfig(), NAMESPACE); when(workspaceDao.get(workspace.getId())).thenReturn(workspace); RuntimeDescriptor descriptor = createDescriptor(workspace, STARTING); when(runtimes.get(any())).thenReturn(descriptor); @@ -1151,7 +1135,7 @@ public class WorkspaceManagerTest { @Test public void shouldBeAbleToGetMachineInstanceIfWorkspaceIsRunning() throws Exception { // given - final WorkspaceImpl workspace = workspaceManager.createWorkspace(createConfig(), "user123", "account"); + final WorkspaceImpl workspace = workspaceManager.createWorkspace(createConfig(), NAMESPACE); when(workspaceDao.get(workspace.getId())).thenReturn(workspace); RuntimeDescriptor descriptor = createDescriptor(workspace, RUNNING); when(runtimes.get(any())).thenReturn(descriptor); @@ -1167,7 +1151,7 @@ public class WorkspaceManagerTest { @Test public void shouldBeAbleToGetMachineInstanceIfWorkspaceIsStarting() throws Exception { // given - final WorkspaceImpl workspace = workspaceManager.createWorkspace(createConfig(), "user123", "account"); + final WorkspaceImpl workspace = workspaceManager.createWorkspace(createConfig(), NAMESPACE); when(workspaceDao.get(workspace.getId())).thenReturn(workspace); RuntimeDescriptor descriptor = createDescriptor(workspace, STARTING); when(runtimes.get(any())).thenReturn(descriptor); @@ -1204,7 +1188,9 @@ public class WorkspaceManagerTest { singletonMap("dev-machine", new ExtendedMachineImpl(singletonList("org.eclipse.che.ws-agent"), null, - new HashMap<>(singletonMap("memoryLimitBytes", "10000"))))); + new HashMap<>( + singletonMap("memoryLimitBytes", + "10000"))))); return WorkspaceConfigImpl.builder() .setName("dev-workspace") .setDefaultEnv("dev-env") diff --git a/wsmaster/che-core-api-workspace/src/test/java/org/eclipse/che/api/workspace/server/WorkspaceRuntimesTest.java b/wsmaster/che-core-api-workspace/src/test/java/org/eclipse/che/api/workspace/server/WorkspaceRuntimesTest.java index f7f10c290b..2bcd4a9c87 100644 --- a/wsmaster/che-core-api-workspace/src/test/java/org/eclipse/che/api/workspace/server/WorkspaceRuntimesTest.java +++ b/wsmaster/che-core-api-workspace/src/test/java/org/eclipse/che/api/workspace/server/WorkspaceRuntimesTest.java @@ -10,6 +10,7 @@ *******************************************************************************/ package org.eclipse.che.api.workspace.server; +import org.eclipse.che.account.spi.AccountImpl; import org.eclipse.che.api.agent.server.AgentRegistry; import org.eclipse.che.api.agent.server.impl.AgentSorter; import org.eclipse.che.api.agent.server.launcher.AgentLauncherFactory; @@ -75,8 +76,9 @@ import static org.testng.Assert.assertNotNull; @Listeners(MockitoTestNGListener.class) public class WorkspaceRuntimesTest { - private static String WORKSPACE_ID = "workspace123"; - private static String ENV_NAME = "default-env"; + private static final String WORKSPACE_ID = "workspace123"; + private static final String ENV_NAME = "default-env"; + private static final String NAMESPACE = "wsNamespace"; @Mock private EventService eventService; @@ -601,7 +603,7 @@ public class WorkspaceRuntimesTest { .setEnvironments(singletonMap(ENV_NAME, environment)) .setDefaultEnv(ENV_NAME) .build(); - return new WorkspaceImpl(WORKSPACE_ID, "user123", wsConfig); + return new WorkspaceImpl(WORKSPACE_ID, new AccountImpl("accountId", "user123", "test"), wsConfig); } private static class TestMachineInstance extends NoOpMachineInstance { diff --git a/wsmaster/che-core-api-workspace/src/test/java/org/eclipse/che/api/workspace/server/WorkspaceServiceTest.java b/wsmaster/che-core-api-workspace/src/test/java/org/eclipse/che/api/workspace/server/WorkspaceServiceTest.java index 4963ad7866..3205d2fbac 100644 --- a/wsmaster/che-core-api-workspace/src/test/java/org/eclipse/che/api/workspace/server/WorkspaceServiceTest.java +++ b/wsmaster/che-core-api-workspace/src/test/java/org/eclipse/che/api/workspace/server/WorkspaceServiceTest.java @@ -15,6 +15,8 @@ import com.google.common.collect.ImmutableMap; import com.google.common.collect.Sets; import com.jayway.restassured.response.Response; +import org.eclipse.che.account.shared.model.Account; +import org.eclipse.che.account.spi.AccountImpl; import org.eclipse.che.api.core.model.machine.MachineStatus; import org.eclipse.che.api.core.model.project.ProjectConfig; import org.eclipse.che.api.core.model.workspace.WorkspaceConfig; @@ -118,11 +120,12 @@ import static org.testng.Assert.assertTrue; public class WorkspaceServiceTest { @SuppressWarnings("unused") - private static final ApiExceptionMapper MAPPER = new ApiExceptionMapper(); - private static final String NAMESPACE = "user"; - private static final String USER_ID = "user123"; + private static final ApiExceptionMapper MAPPER = new ApiExceptionMapper(); + private static final String NAMESPACE = "user"; + private static final String USER_ID = "user123"; + private static final Account TEST_ACCOUNT = new AccountImpl("anyId", NAMESPACE, "test"); @SuppressWarnings("unused") - private static final EnvironmentFilter FILTER = new EnvironmentFilter(); + private static final EnvironmentFilter FILTER = new EnvironmentFilter(); @Mock private WorkspaceManager wsManager; @@ -144,7 +147,35 @@ public class WorkspaceServiceTest { public void shouldCreateWorkspace() throws Exception { final WorkspaceConfigDto configDto = createConfigDto(); final WorkspaceImpl workspace = createWorkspace(configDto); - when(wsManager.createWorkspace(any(), any(), any(), any())).thenReturn(workspace); + when(wsManager.createWorkspace(any(), any(), any())).thenReturn(workspace); + + final Response response = given().auth() + .basic(ADMIN_USER_NAME, ADMIN_USER_PASSWORD) + .contentType("application/json") + .body(configDto) + .when() + .post(SECURE_PATH + "/workspace" + + "?namespace=test" + + "&attribute=stackId:stack123" + + "&attribute=factoryId:factory123" + + "&attribute=custom:custom:value"); + + assertEquals(response.getStatusCode(), 201); + assertEquals(new WorkspaceImpl(unwrapDto(response, WorkspaceDto.class), TEST_ACCOUNT), workspace); + verify(validator).validateConfig(any()); + verify(validator).validateAttributes(any()); + verify(wsManager).createWorkspace(anyObject(), + eq("test"), + eq(ImmutableMap.of("stackId", "stack123", + "factoryId", "factory123", + "custom", "custom:value"))); + } + + @Test + public void shouldUseUsernameAsNamespaceWhenCreatingWorkspaceWithoutSpecifiedNamespace() throws Exception { + final WorkspaceConfigDto configDto = createConfigDto(); + final WorkspaceImpl workspace = createWorkspace(configDto); + when(wsManager.createWorkspace(any(), any(), any())).thenReturn(workspace); final Response response = given().auth() .basic(ADMIN_USER_NAME, ADMIN_USER_PASSWORD) @@ -157,22 +188,21 @@ public class WorkspaceServiceTest { "&attribute=custom:custom:value"); assertEquals(response.getStatusCode(), 201); - assertEquals(new WorkspaceImpl(unwrapDto(response, WorkspaceDto.class)), workspace); + assertEquals(new WorkspaceImpl(unwrapDto(response, WorkspaceDto.class), TEST_ACCOUNT), workspace); verify(validator).validateConfig(any()); verify(validator).validateAttributes(any()); verify(wsManager).createWorkspace(anyObject(), - anyString(), + eq(NAMESPACE), eq(ImmutableMap.of("stackId", "stack123", "factoryId", "factory123", - "custom", "custom:value")), - eq(null)); + "custom", "custom:value"))); } @Test public void shouldStartTheWorkspaceAfterItIsCreatedWhenStartAfterCreateParamIsTrue() throws Exception { final WorkspaceConfigDto configDto = createConfigDto(); final WorkspaceImpl workspace = createWorkspace(configDto); - when(wsManager.createWorkspace(any(), any(), any(), any())).thenReturn(workspace); + when(wsManager.createWorkspace(any(), any(), any())).thenReturn(workspace); given().auth() .basic(ADMIN_USER_NAME, ADMIN_USER_PASSWORD) @@ -185,13 +215,12 @@ public class WorkspaceServiceTest { "&attribute=custom:custom:value" + "&start-after-create=true"); - verify(wsManager).startWorkspace(workspace.getId(), null, null, false); + verify(wsManager).startWorkspace(workspace.getId(), null, false); verify(wsManager).createWorkspace(anyObject(), anyString(), eq(ImmutableMap.of("stackId", "stack123", "factoryId", "factory123", - "custom", "custom:value")), - eq(null)); + "custom", "custom:value"))); } @Test @@ -232,7 +261,7 @@ public class WorkspaceServiceTest { .get(SECURE_PATH + "/workspace/" + workspace.getId()); assertEquals(response.getStatusCode(), 200); - assertEquals(new WorkspaceImpl(unwrapDto(response, WorkspaceDto.class)), workspace); + assertEquals(new WorkspaceImpl(unwrapDto(response, WorkspaceDto.class), TEST_ACCOUNT), workspace); } @Test @@ -248,7 +277,25 @@ public class WorkspaceServiceTest { assertEquals(response.getStatusCode(), 200); assertEquals(unwrapDtoList(response, WorkspaceDto.class).stream() - .map(WorkspaceImpl::new) + .map(ws -> new WorkspaceImpl(ws, TEST_ACCOUNT)) + .collect(toList()), + asList(workspace1, workspace2)); + } + + @Test + public void shouldGetWorkspacesByNamespace() throws Exception { + final WorkspaceImpl workspace1 = createWorkspace(createConfigDto()); + final WorkspaceImpl workspace2 = createWorkspace(createConfigDto(), STARTING); + when(wsManager.getByNamespace(NAMESPACE)).thenReturn(asList(workspace1, workspace2)); + + final Response response = given().auth() + .basic(ADMIN_USER_NAME, ADMIN_USER_PASSWORD) + .when() + .get(SECURE_PATH + "/workspace/namespace/" + NAMESPACE); + + assertEquals(response.getStatusCode(), 200); + assertEquals(unwrapDtoList(response, WorkspaceDto.class).stream() + .map(ws -> new WorkspaceImpl(ws, TEST_ACCOUNT)) .collect(toList()), asList(workspace1, workspace2)); } @@ -266,7 +313,7 @@ public class WorkspaceServiceTest { assertEquals(response.getStatusCode(), 200); assertEquals(unwrapDtoList(response, WorkspaceDto.class).stream() - .map(WorkspaceImpl::new) + .map(ws -> new WorkspaceImpl(ws, TEST_ACCOUNT)) .collect(toList()), singletonList(workspace2)); } @@ -286,7 +333,7 @@ public class WorkspaceServiceTest { .put(SECURE_PATH + "/workspace/" + workspace.getId()); assertEquals(response.getStatusCode(), 200); - assertEquals(new WorkspaceImpl(unwrapDto(response, WorkspaceDto.class)), workspace); + assertEquals(new WorkspaceImpl(unwrapDto(response, WorkspaceDto.class), TEST_ACCOUNT), workspace); verify(validator).validateWorkspace(any()); } @@ -310,7 +357,7 @@ public class WorkspaceServiceTest { @Test public void shouldStartWorkspace() throws Exception { final WorkspaceImpl workspace = createWorkspace(createConfigDto()); - when(wsManager.startWorkspace(any(), any(), any(), any())).thenReturn(workspace); + when(wsManager.startWorkspace(any(), any(), any())).thenReturn(workspace); when(wsManager.getWorkspace(workspace.getId())).thenReturn(workspace); final Response response = given().auth() @@ -320,14 +367,14 @@ public class WorkspaceServiceTest { "?environment=" + workspace.getConfig().getDefaultEnv()); assertEquals(response.getStatusCode(), 200); - assertEquals(new WorkspaceImpl(unwrapDto(response, WorkspaceDto.class)), workspace); - verify(wsManager).startWorkspace(workspace.getId(), workspace.getConfig().getDefaultEnv(), null, null); + assertEquals(new WorkspaceImpl(unwrapDto(response, WorkspaceDto.class), TEST_ACCOUNT), workspace); + verify(wsManager).startWorkspace(workspace.getId(), workspace.getConfig().getDefaultEnv(), null); } @Test public void shouldRestoreWorkspace() throws Exception { final WorkspaceImpl workspace = createWorkspace(createConfigDto()); - when(wsManager.startWorkspace(any(), any(), any(), any())).thenReturn(workspace); + when(wsManager.startWorkspace(any(), any(), any())).thenReturn(workspace); when(wsManager.getWorkspace(workspace.getId())).thenReturn(workspace); final Response response = given().auth() @@ -337,14 +384,14 @@ public class WorkspaceServiceTest { "?environment=" + workspace.getConfig().getDefaultEnv() + "&restore=true"); assertEquals(response.getStatusCode(), 200); - assertEquals(new WorkspaceImpl(unwrapDto(response, WorkspaceDto.class)), workspace); - verify(wsManager).startWorkspace(workspace.getId(), workspace.getConfig().getDefaultEnv(), null, true); + assertEquals(new WorkspaceImpl(unwrapDto(response, WorkspaceDto.class), TEST_ACCOUNT), workspace); + verify(wsManager).startWorkspace(workspace.getId(), workspace.getConfig().getDefaultEnv(), true); } @Test public void shouldNotRestoreWorkspace() throws Exception { final WorkspaceImpl workspace = createWorkspace(createConfigDto()); - when(wsManager.startWorkspace(any(), any(), any(), any())).thenReturn(workspace); + when(wsManager.startWorkspace(any(), any(), any())).thenReturn(workspace); when(wsManager.getWorkspace(workspace.getId())).thenReturn(workspace); final Response response = given().auth() @@ -354,8 +401,8 @@ public class WorkspaceServiceTest { "?environment=" + workspace.getConfig().getDefaultEnv() + "&restore=false"); assertEquals(response.getStatusCode(), 200); - assertEquals(new WorkspaceImpl(unwrapDto(response, WorkspaceDto.class)), workspace); - verify(wsManager).startWorkspace(workspace.getId(), workspace.getConfig().getDefaultEnv(), null, false); + assertEquals(new WorkspaceImpl(unwrapDto(response, WorkspaceDto.class), TEST_ACCOUNT), workspace); + verify(wsManager).startWorkspace(workspace.getId(), workspace.getConfig().getDefaultEnv(), false); } @Test @@ -363,8 +410,7 @@ public class WorkspaceServiceTest { final WorkspaceImpl workspace = createWorkspace(createConfigDto()); when(wsManager.startWorkspace(anyObject(), anyString(), - anyBoolean(), - anyString())).thenReturn(workspace); + anyBoolean())).thenReturn(workspace); when(wsManager.getWorkspace(workspace.getId())).thenReturn(workspace); final WorkspaceDto workspaceDto = DtoConverter.asDto(workspace); @@ -373,10 +419,39 @@ public class WorkspaceServiceTest { .contentType("application/json") .body(workspaceDto.getConfig()) .when() - .post(SECURE_PATH + "/workspace/runtime"); + .post(SECURE_PATH + "/workspace/runtime" + + "?namespace=test" + + "&temporary=true"); assertEquals(response.getStatusCode(), 200); verify(validator).validateConfig(any()); + verify(wsManager).startWorkspace(any(), + eq("test"), + eq(true)); + } + + @Test + public void shouldUseUsernameAsNamespaceWhenStartingWorkspaceFromConfigWithoutNamespace() throws Exception { + final WorkspaceImpl workspace = createWorkspace(createConfigDto()); + when(wsManager.startWorkspace(anyObject(), + anyString(), + anyBoolean())).thenReturn(workspace); + when(wsManager.getWorkspace(workspace.getId())).thenReturn(workspace); + final WorkspaceDto workspaceDto = DtoConverter.asDto(workspace); + + final Response response = given().auth() + .basic(ADMIN_USER_NAME, ADMIN_USER_PASSWORD) + .contentType("application/json") + .body(workspaceDto.getConfig()) + .when() + .post(SECURE_PATH + "/workspace/runtime" + + "?temporary=true"); + + assertEquals(response.getStatusCode(), 200); + verify(validator).validateConfig(any()); + verify(wsManager).startWorkspace(any(), + eq(NAMESPACE), + eq(true)); } @Test @@ -423,7 +498,7 @@ public class WorkspaceServiceTest { .post(SECURE_PATH + "/workspace/" + workspace.getId() + "/command"); assertEquals(response.getStatusCode(), 200); - assertEquals(new WorkspaceImpl(unwrapDto(response, WorkspaceDto.class)) + assertEquals(new WorkspaceImpl(unwrapDto(response, WorkspaceDto.class), TEST_ACCOUNT) .getConfig() .getCommands() .size(), commandsSizeBefore + 1); @@ -502,7 +577,7 @@ public class WorkspaceServiceTest { .post(SECURE_PATH + "/workspace/" + workspace.getId() + "/environment"); assertEquals(response.getStatusCode(), 200); - assertEquals(new WorkspaceImpl(unwrapDto(response, WorkspaceDto.class)) + assertEquals(new WorkspaceImpl(unwrapDto(response, WorkspaceDto.class), TEST_ACCOUNT) .getConfig() .getEnvironments() .size(), envsSizeBefore + 1); @@ -582,7 +657,7 @@ public class WorkspaceServiceTest { .post(SECURE_PATH + "/workspace/" + workspace.getId() + "/project"); assertEquals(response.getStatusCode(), 200); - assertEquals(new WorkspaceImpl(unwrapDto(response, WorkspaceDto.class)) + assertEquals(new WorkspaceImpl(unwrapDto(response, WorkspaceDto.class), TEST_ACCOUNT) .getConfig() .getProjects() .size(), projectsSizeBefore + 1); @@ -729,7 +804,6 @@ public class WorkspaceServiceTest { .setMachineName("machine1") .setMachineSource(new MachineSourceImpl("type") .setContent("content")) - .setNamespace("namespace") .setType("type") .setWorkspaceId(workspaceId); SnapshotImpl snapshot1 = snapshotBuilder.build(); @@ -791,7 +865,7 @@ public class WorkspaceServiceTest { return WorkspaceImpl.builder() .setConfig(configDto) .generateId() - .setNamespace(NAMESPACE) + .setAccount(TEST_ACCOUNT) .setStatus(status) .build(); } diff --git a/wsmaster/che-core-api-workspace/src/test/java/org/eclipse/che/api/workspace/server/jpa/JpaWorkspaceDaoTest.java b/wsmaster/che-core-api-workspace/src/test/java/org/eclipse/che/api/workspace/server/jpa/JpaWorkspaceDaoTest.java new file mode 100644 index 0000000000..1c92e08778 --- /dev/null +++ b/wsmaster/che-core-api-workspace/src/test/java/org/eclipse/che/api/workspace/server/jpa/JpaWorkspaceDaoTest.java @@ -0,0 +1,88 @@ +/******************************************************************************* + * Copyright (c) 2012-2016 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 + *******************************************************************************/ +package org.eclipse.che.api.workspace.server.jpa; + +import com.google.inject.Guice; + +import org.eclipse.che.account.spi.AccountImpl; +import org.eclipse.che.api.workspace.server.model.impl.ProjectConfigImpl; +import org.eclipse.che.api.workspace.server.model.impl.WorkspaceConfigImpl; +import org.eclipse.che.api.workspace.server.model.impl.WorkspaceImpl; +import org.testng.annotations.AfterMethod; +import org.testng.annotations.BeforeMethod; +import org.testng.annotations.Test; + +import javax.persistence.EntityManager; + +import java.util.Collections; +import java.util.HashMap; + +import static java.util.Arrays.asList; +import static org.eclipse.che.api.workspace.server.spi.tck.WorkspaceDaoTest.createWorkspace; +import static org.testng.Assert.assertEquals; + +/** + * Tests JPA specific use-cases. + * + * @author Yevhenii Voevodin + */ +public class JpaWorkspaceDaoTest { + + private EntityManager manager; + + @BeforeMethod + private void setUpManager() { + manager = Guice.createInjector(new WorkspaceTckModule()).getInstance(EntityManager.class); + } + + @AfterMethod + private void cleanup() { + manager.getEntityManagerFactory().close(); + } + + @Test + public void shouldCascadeRemoveObjectsWhenTheyRemovedFromEntity() { + final AccountImpl account = new AccountImpl("accountId", "namespace", "test"); + final WorkspaceImpl workspace = createWorkspace("id", account, "name"); + + // Persist the workspace + manager.getTransaction().begin(); + manager.persist(account); + manager.getTransaction().commit(); + manager.clear(); + + // Persist the workspace + manager.getTransaction().begin(); + manager.persist(workspace); + manager.getTransaction().commit(); + manager.clear(); + + // Cleanup one to many dependencies + manager.getTransaction().begin(); + final WorkspaceConfigImpl config = workspace.getConfig(); + config.getProjects().clear(); + config.getCommands().clear(); + config.getEnvironments().clear(); + manager.merge(workspace); + manager.getTransaction().commit(); + manager.clear(); + + // If all the One To Many dependencies are removed then all the embedded objects + // which depend on those object are also removed, which guaranteed by foreign key constraints + assertEquals(asLong("SELECT COUNT(p) FROM ProjectConfig p"), 0L, "Project configs"); + assertEquals(asLong("SELECT COUNT(c) FROM Command c"), 0L, "Commands"); + assertEquals(asLong("SELECT COUNT(e) FROM Environment e"), 0L, "Environments"); + } + + private long asLong(String query) { + return manager.createQuery(query, Long.class).getSingleResult(); + } +} diff --git a/wsmaster/che-core-api-workspace/src/test/java/org/eclipse/che/api/workspace/server/jpa/WorkspaceTckModule.java b/wsmaster/che-core-api-workspace/src/test/java/org/eclipse/che/api/workspace/server/jpa/WorkspaceTckModule.java new file mode 100644 index 0000000000..dc3e5187bd --- /dev/null +++ b/wsmaster/che-core-api-workspace/src/test/java/org/eclipse/che/api/workspace/server/jpa/WorkspaceTckModule.java @@ -0,0 +1,46 @@ +/******************************************************************************* + * Copyright (c) 2012-2016 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 + *******************************************************************************/ +package org.eclipse.che.api.workspace.server.jpa; + +import com.google.inject.TypeLiteral; +import com.google.inject.persist.jpa.JpaPersistModule; + +import org.eclipse.che.account.spi.AccountImpl; +import org.eclipse.che.api.core.jdbc.jpa.eclipselink.EntityListenerInjectionManagerInitializer; +import org.eclipse.che.api.core.jdbc.jpa.guice.JpaInitializer; +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.test.tck.TckModule; +import org.eclipse.che.commons.test.tck.repository.JpaTckRepository; +import org.eclipse.che.commons.test.tck.repository.TckRepository; + +/** + * @author Yevhenii Voevodin + */ +public class WorkspaceTckModule extends TckModule { + + @Override + protected void configure() { + install(new JpaPersistModule("main")); + bind(JpaInitializer.class).asEagerSingleton(); + bind(EntityListenerInjectionManagerInitializer.class).asEagerSingleton(); + bind(org.eclipse.che.api.core.h2.jdbc.jpa.eclipselink.H2ExceptionHandler.class); + + bind(new TypeLiteral>() {}).toInstance(new JpaTckRepository<>(AccountImpl.class)); + bind(new TypeLiteral>() {}).toInstance(new JpaTckRepository<>(WorkspaceImpl.class)); + bind(new TypeLiteral>() {}).toInstance(new JpaTckRepository<>(StackImpl.class)); + + bind(WorkspaceDao.class).to(JpaWorkspaceDao.class); + bind(StackDao.class).to(JpaStackDao.class); + } +} diff --git a/wsmaster/che-core-api-workspace/src/test/java/org/eclipse/che/api/workspace/server/spi/tck/StackDaoTest.java b/wsmaster/che-core-api-workspace/src/test/java/org/eclipse/che/api/workspace/server/spi/tck/StackDaoTest.java new file mode 100644 index 0000000000..e046c8c50e --- /dev/null +++ b/wsmaster/che-core-api-workspace/src/test/java/org/eclipse/che/api/workspace/server/spi/tck/StackDaoTest.java @@ -0,0 +1,245 @@ +/******************************************************************************* + * Copyright (c) 2012-2016 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 + *******************************************************************************/ +package org.eclipse.che.api.workspace.server.spi.tck; + +import com.google.inject.Inject; + +import org.eclipse.che.api.core.ConflictException; +import org.eclipse.che.api.core.NotFoundException; +import org.eclipse.che.api.core.ServerException; +import org.eclipse.che.api.machine.server.spi.SnapshotDao; +import org.eclipse.che.api.workspace.server.model.impl.stack.StackComponentImpl; +import org.eclipse.che.api.workspace.server.model.impl.stack.StackImpl; +import org.eclipse.che.api.workspace.server.model.impl.stack.StackSourceImpl; +import org.eclipse.che.api.workspace.server.spi.StackDao; +import org.eclipse.che.api.workspace.server.stack.image.StackIcon; +import org.eclipse.che.commons.test.tck.TckModuleFactory; +import org.eclipse.che.commons.test.tck.repository.TckRepository; +import org.eclipse.che.commons.test.tck.repository.TckRepositoryException; +import org.testng.annotations.AfterMethod; +import org.testng.annotations.BeforeMethod; +import org.testng.annotations.Guice; +import org.testng.annotations.Test; + +import java.util.Collections; +import java.util.HashSet; +import java.util.List; + +import static java.util.Arrays.asList; +import static org.eclipse.che.api.workspace.server.spi.tck.WorkspaceDaoTest.createWorkspaceConfig; +import static org.testng.Assert.assertEquals; + +/** + * Tests {@link SnapshotDao} contract. + * + * @author Yevhenii Voevodin + */ +@Guice(moduleFactory = TckModuleFactory.class) +@Test(suiteName = StackDaoTest.SUITE_NAME) +public class StackDaoTest { + + public static final String SUITE_NAME = "StackDaoTck"; + + private static final int STACKS_SIZE = 5; + + private StackImpl[] stacks; + + @Inject + private TckRepository stackRepo; + + @Inject + private StackDao stackDao; + + @BeforeMethod + private void createStacks() throws TckRepositoryException { + stacks = new StackImpl[STACKS_SIZE]; + for (int i = 0; i < STACKS_SIZE; i++) { + stacks[i] = createStack("stack-" + i, "name-" + i); + } + stackRepo.createAll(asList(stacks)); + } + + @AfterMethod + private void removeStacks() throws TckRepositoryException { + stackRepo.removeAll(); + } + + @Test + public void shouldGetById() throws Exception { + final StackImpl stack = stacks[0]; + + assertEquals(stackDao.getById(stack.getId()), stack); + } + + @Test(expectedExceptions = NotFoundException.class) + public void shouldThrowNotFoundExceptionWhenGettingNonExistingStack() throws Exception { + stackDao.getById("non-existing-stack"); + } + + @Test(expectedExceptions = NullPointerException.class) + public void shouldThrowNpeWhenGettingStackByNullKey() throws Exception { + stackDao.getById(null); + } + + @Test(dependsOnMethods = "shouldGetById") + public void shouldCreateStack() throws Exception { + final StackImpl stack = createStack("new-stack", "new-stack-name"); + + stackDao.create(stack); + + assertEquals(stackDao.getById(stack.getId()), stack); + } + + @Test(expectedExceptions = ConflictException.class) + public void shouldThrowConflictExceptionWhenCreatingStackWithIdThatAlreadyExists() throws Exception { + final StackImpl stack = createStack(stacks[0].getId(), "new-name"); + + stackDao.create(stack); + } + + @Test(expectedExceptions = ConflictException.class) + public void shouldThrowConflictExceptionWhenCreatingStackWithNameThatAlreadyExists() throws Exception { + final StackImpl stack = createStack("new-stack-id", stacks[0].getName()); + + stackDao.create(stack); + } + + @Test(expectedExceptions = NullPointerException.class) + public void shouldThrowNpeWhenCreatingNullStack() throws Exception { + stackDao.create(null); + } + + @Test(expectedExceptions = NotFoundException.class, + dependsOnMethods = "shouldThrowNotFoundExceptionWhenGettingNonExistingStack") + public void shouldRemoveStack() throws Exception { + final StackImpl stack = stacks[0]; + + stackDao.remove(stack.getId()); + + // Should throw an exception + stackDao.getById(stack.getId()); + } + + @Test + public void shouldNotThrowAnyExceptionWhenRemovingNonExistingStack() throws Exception { + stackDao.remove("non-existing"); + } + + @Test(expectedExceptions = NullPointerException.class) + public void shouldThrowNpeWhenRemovingNull() throws Exception { + stackDao.remove(null); + } + + @Test(dependsOnMethods = "shouldGetById") + public void shouldUpdateStack() throws Exception { + final StackImpl stack = stacks[0]; + + stack.setName("new-name"); + stack.setCreator("new-creator"); + stack.setDescription("new-description"); + stack.setScope("new-scope"); + stack.getTags().clear(); + stack.getTags().add("new-tag"); + + // Remove an existing component + stack.getComponents().remove(1); + + // Add a new component + stack.getComponents().add(new StackComponentImpl("component3", "component3-version")); + + // Update an existing component + final StackComponentImpl component = stack.getComponents().get(0); + component.setName("new-name"); + component.setVersion("new-version"); + + // Updating source + final StackSourceImpl source = stack.getSource(); + source.setType("new-type"); + source.setOrigin("new-source"); + + // Set a new icon + stack.setStackIcon(new StackIcon("new-name", "new-media", "new-data".getBytes())); + + stackDao.update(stack); + + assertEquals(stackDao.getById(stack.getId()), new StackImpl(stack)); + } + + @Test(expectedExceptions = ConflictException.class) + public void shouldNotUpdateStackIfNewNameIsReserved() throws Exception { + final StackImpl stack = stacks[0]; + stack.setName(stacks[1].getName()); + + stackDao.update(stack); + } + + @Test(expectedExceptions = NotFoundException.class) + public void shouldThrowNotFoundExceptionWhenUpdatingNonExistingStack() throws Exception { + stackDao.update(createStack("new-stack", "new-stack-name")); + } + + @Test(expectedExceptions = NullPointerException.class) + public void shouldThrowNpeWhenUpdatingNullStack() throws Exception { + stackDao.update(null); + } + + @Test(dependsOnMethods = "shouldUpdateStack") + public void shouldFindStacksWithSpecifiedTags() throws Exception { + stacks[0].getTags().addAll(asList("search-tag1", "search-tag2")); + stacks[1].getTags().addAll(asList("search-tag1", "non-search-tag")); + stacks[2].getTags().addAll(asList("non-search-tag", "search-tag2")); + stacks[3].getTags().addAll(asList("search-tag1", "search-tag2", "another-tag")); + updateAll(); + + final List found = stackDao.searchStacks(null, asList("search-tag1", "search-tag2"), 0, 0); + found.forEach(s -> Collections.sort(s.getTags())); + for (StackImpl stack : stacks) { + Collections.sort(stack.getTags()); + } + + assertEquals(new HashSet<>(found), new HashSet<>(asList(stacks[0], stacks[3]))); + } + + @Test + public void shouldReturnAllStacksWhenSearchingWithoutTags() throws Exception { + final List found = stackDao.searchStacks(null, null, 0, 0); + found.forEach(s -> Collections.sort(s.getTags())); + for (StackImpl stack : stacks) { + Collections.sort(stack.getTags()); + } + + assertEquals(new HashSet<>(found), new HashSet<>(asList(stacks))); + } + + private void updateAll() throws ConflictException, NotFoundException, ServerException { + for (StackImpl stack : stacks) { + stackDao.update(stack); + } + } + + private static StackImpl createStack(String id, String name) { + return StackImpl.builder() + .setId(id) + .setName(name) + .setCreator("user123") + .setDescription(id + "-description") + .setScope(id + "-scope") + .setWorkspaceConfig(createWorkspaceConfig("test")) + .setTags(asList(id + "-tag1", id + "-tag2")) + .setComponents(asList(new StackComponentImpl(id + "-component1", id + "-component1-version"), + new StackComponentImpl(id + "-component2", id + "-component2-version"))) + .setSource(new StackSourceImpl(id + "-type", id + "-origin")) + .setStackIcon(new StackIcon(id + "-icon", + id + "-media-type", + "0x1234567890abcdef".getBytes())) + .build(); + } +} diff --git a/wsmaster/che-core-api-workspace/src/test/java/org/eclipse/che/api/workspace/server/spi/tck/WorkspaceDaoTest.java b/wsmaster/che-core-api-workspace/src/test/java/org/eclipse/che/api/workspace/server/spi/tck/WorkspaceDaoTest.java new file mode 100644 index 0000000000..f4e8b32ecb --- /dev/null +++ b/wsmaster/che-core-api-workspace/src/test/java/org/eclipse/che/api/workspace/server/spi/tck/WorkspaceDaoTest.java @@ -0,0 +1,486 @@ +/******************************************************************************* + * Copyright (c) 2012-2016 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 + *******************************************************************************/ +package org.eclipse.che.api.workspace.server.spi.tck; + +import com.google.common.collect.ImmutableList; +import com.google.common.collect.ImmutableMap; + +import org.eclipse.che.account.spi.AccountImpl; +import org.eclipse.che.api.core.ConflictException; +import org.eclipse.che.api.core.NotFoundException; +import org.eclipse.che.api.core.model.workspace.WorkspaceStatus; +import org.eclipse.che.api.machine.server.model.impl.CommandImpl; +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.spi.WorkspaceDao; +import org.eclipse.che.commons.test.tck.TckModuleFactory; +import org.eclipse.che.commons.test.tck.repository.TckRepository; +import org.eclipse.che.commons.test.tck.repository.TckRepositoryException; +import org.testng.annotations.AfterMethod; +import org.testng.annotations.BeforeMethod; +import org.testng.annotations.Guice; +import org.testng.annotations.Test; + +import javax.inject.Inject; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; + +import static java.util.Arrays.asList; +import static java.util.Collections.singletonMap; +import static org.testng.Assert.assertEquals; +import static org.testng.Assert.assertTrue; + +/** + * Tests {@link WorkspaceDao} contract. + * + * @author Yevhenii Voevodin + */ +@Guice(moduleFactory = TckModuleFactory.class) +@Test(suiteName = WorkspaceDaoTest.SUITE_NAME) +public class WorkspaceDaoTest { + + public static final String SUITE_NAME = "WorkspaceDaoTck"; + + private static final int COUNT_OF_WORKSPACES = 5; + private static final int COUNT_OF_ACCOUNTS = 3; + + @Inject + private TckRepository workspaceRepo; + + @Inject + private TckRepository accountRepo; + + @Inject + private WorkspaceDao workspaceDao; + + private AccountImpl[] accounts; + + private WorkspaceImpl[] workspaces; + + @AfterMethod + public void removeEntities() throws TckRepositoryException { + workspaceRepo.removeAll(); + accountRepo.removeAll(); + } + + @BeforeMethod + public void createEntities() throws TckRepositoryException { + accounts = new AccountImpl[COUNT_OF_ACCOUNTS]; + for (int i = 0; i < COUNT_OF_ACCOUNTS; i++) { + accounts[i] = new AccountImpl("accountId" + i, "accountName" + i, "test"); + } + workspaces = new WorkspaceImpl[COUNT_OF_WORKSPACES]; + for (int i = 0; i < COUNT_OF_WORKSPACES; i++) { + // 2 workspaces share 1 namespace + workspaces[i] = createWorkspace("workspace-" + i, accounts[i / 2], "name-" + i); + } + accountRepo.createAll(asList(accounts)); + workspaceRepo.createAll(asList(workspaces)); + } + + @Test + public void shouldGetWorkspaceById() throws Exception { + final WorkspaceImpl workspace = workspaces[0]; + + assertEquals(workspaceDao.get(workspace.getId()), workspace); + } + + @Test(expectedExceptions = NotFoundException.class) + public void shouldThrowNotFoundExceptionWhenGettingNonExistingWorkspaceById() throws Exception { + workspaceDao.get("non-existing-id"); + } + + @Test(expectedExceptions = NullPointerException.class) + public void shouldThrowNpeWhenGettingWorkspaceByIdWhereIdIsNull() throws Exception { + workspaceDao.get(null); + } + + @Test + public void shouldGetWorkspacesByNamespace() throws Exception { + final WorkspaceImpl workspace1 = workspaces[0]; + final WorkspaceImpl workspace2 = workspaces[1]; + assertEquals(workspace1.getNamespace(), workspace2.getNamespace(), "Namespaces must be the same"); + + final List found = workspaceDao.getByNamespace(workspace1.getNamespace()); + + assertEquals(new HashSet<>(found), new HashSet<>(asList(workspace1, workspace2))); + } + + @Test + public void emptyListShouldBeReturnedWhenThereAreNoWorkspacesInGivenNamespace() throws Exception { + assertTrue(workspaceDao.getByNamespace("non-existing-namespace").isEmpty()); + } + + @Test(expectedExceptions = NullPointerException.class) + public void shouldThrowNpeWhenGettingWorkspaceByNullNamespace() throws Exception { + workspaceDao.getByNamespace(null); + } + + @Test + public void shouldGetWorkspaceByNameAndNamespace() throws Exception { + final WorkspaceImpl workspace = workspaces[0]; + + assertEquals(workspaceDao.get(workspace.getName(), workspace.getNamespace()), workspace); + } + + @Test(expectedExceptions = NotFoundException.class) + public void shouldThrowNotFoundExceptionWhenWorkspaceWithSuchNameDoesNotExist() throws Exception { + final WorkspaceImpl workspace = workspaces[0]; + + workspaceDao.get("non-existing-name", workspace.getNamespace()); + } + + @Test(expectedExceptions = NotFoundException.class) + public void shouldThrowNotFoundExceptionWhenWorkspaceWithSuchNamespaceDoesNotExist() throws Exception { + final WorkspaceImpl workspace = workspaces[0]; + + workspaceDao.get(workspace.getName(), "non-existing-namespace"); + } + + @Test(expectedExceptions = NotFoundException.class) + public void shouldThrowNotFoundExceptionWhenWorkspaceWithSuchNameDoesNotExistInGiveWorkspace() throws Exception { + final WorkspaceImpl workspace1 = workspaces[0]; + final WorkspaceImpl workspace2 = workspaces[2]; + + workspaceDao.get(workspace1.getName(), workspace2.getNamespace()); + } + + @Test(expectedExceptions = NullPointerException.class) + public void shouldThrowNpeWhenGettingWorkspaceByNameAndNamespaceWhereNameIsNull() throws Exception { + workspaceDao.get(null, workspaces[0].getNamespace()); + } + + @Test(expectedExceptions = NullPointerException.class) + public void shouldThrowNpeWhenGettingWorkspaceByNameAndNamespaceWhereNamespaceIsNull() throws Exception { + workspaceDao.get(workspaces[0].getName(), null); + } + + @Test(expectedExceptions = NotFoundException.class, + dependsOnMethods = "shouldThrowNotFoundExceptionWhenGettingNonExistingWorkspaceById") + public void shouldRemoveWorkspace() throws Exception { + final WorkspaceImpl workspace = workspaces[0]; + + workspaceDao.remove(workspace.getId()); + workspaceDao.get(workspace.getId()); + } + + @Test + public void shouldNotThrowExceptionWhenRemovingNonExistingWorkspace() throws Exception { + workspaceDao.remove("non-existing-id"); + } + + @Test(expectedExceptions = NullPointerException.class) + public void shouldThrowNpeWhenRemovingNull() throws Exception { + workspaceDao.remove(null); + } + + @Test(dependsOnMethods = "shouldGetWorkspaceById") + public void shouldCreateWorkspace() throws Exception { + final WorkspaceImpl workspace = createWorkspace("new-workspace", accounts[0], "new-name"); + + workspaceDao.create(workspace); + + assertEquals(workspaceDao.get(workspace.getId()), new WorkspaceImpl(workspace, workspace.getAccount())); + } + + @Test(expectedExceptions = ConflictException.class) + public void shouldNotCreateWorkspaceWithANameWhichAlreadyExistsInGivenNamespace() throws Exception { + final WorkspaceImpl workspace = workspaces[0]; + + final WorkspaceImpl newWorkspace = createWorkspace("new-id", workspace.getAccount(), workspace.getName()); + + workspaceDao.create(newWorkspace); + } + + @Test + public void shouldCreateWorkspaceWithNameWhichDoesNotExistInGivenNamespace() throws Exception { + final WorkspaceImpl workspace = workspaces[0]; + final WorkspaceImpl workspace2 = workspaces[4]; + + final WorkspaceImpl newWorkspace = createWorkspace("new-id", workspace.getAccount(), workspace2.getName()); + final WorkspaceImpl expected = new WorkspaceImpl(newWorkspace, newWorkspace.getAccount()); + expected.setAccount(newWorkspace.getAccount()); + assertEquals(workspaceDao.create(newWorkspace), expected); + } + + @Test(expectedExceptions = ConflictException.class) + public void shouldThrowConflictExceptionWhenCreatingWorkspaceWithExistingId() throws Exception { + final WorkspaceImpl workspace = workspaces[0]; + + final WorkspaceImpl newWorkspace = createWorkspace(workspace.getId(), accounts[0], "new-name"); + + workspaceDao.create(newWorkspace); + } + + @Test(dependsOnMethods = "shouldGetWorkspaceById") + public void shouldUpdateWorkspace() throws Exception { +// final WorkspaceImpl workspace = new WorkspaceImpl(workspaces[0], workspaces[0].getAccount()); +// +// // Remove an existing project configuration from workspace +// workspace.getConfig().getProjects().remove(1); +// +// // Add new project to the workspace configuration +// final SourceStorageImpl source3 = new SourceStorageImpl(); +// source3.setType("type3"); +// source3.setLocation("location3"); +// source3.setParameters(new HashMap<>(ImmutableMap.of("param1", "value1", +// "param2", "value2", +// "param3", "value3"))); +// final ProjectConfigImpl newProjectCfg = new ProjectConfigImpl(); +// newProjectCfg.setPath("/path3"); +// newProjectCfg.setType("type3"); +// newProjectCfg.setName("project3"); +// newProjectCfg.setDescription("description3"); +// newProjectCfg.getMixins().addAll(asList("mixin3", "mixin4")); +// newProjectCfg.setSource(source3); +// newProjectCfg.getAttributes().put("new-key", asList("1", "2")); +// workspace.getConfig().getProjects().add(newProjectCfg); +// +// // Update an existing project configuration +// final ProjectConfigImpl projectCfg = workspace.getConfig().getProjects().get(0); +// projectCfg.getAttributes().clear(); +// projectCfg.getSource().setLocation("new-location"); +// projectCfg.getSource().setType("new-type"); +// projectCfg.getSource().getParameters().put("new-param", "new-param-value"); +// projectCfg.getMixins().add("new-mixin"); +// projectCfg.setPath("/new-path"); +// projectCfg.setDescription("new project description"); +// +// // Remove an existing command +// workspace.getConfig().getCommands().remove(1); +// +// // Add a new command +// final CommandImpl newCmd = new CommandImpl(); +// newCmd.setName("name3"); +// newCmd.setType("type3"); +// newCmd.setCommandLine("cmd3"); +// newCmd.getAttributes().putAll(ImmutableMap.of("attr1", "value1", +// "attr2", "value2", +// "attr3", "value3")); +// workspace.getConfig().getCommands().add(newCmd); +// +// // Update an existing command +// final CommandImpl command = workspace.getConfig().getCommands().get(0); +// command.setName("new-name"); +// command.setType("new-type"); +// command.setCommandLine("new-command-line"); +// command.getAttributes().clear(); +// +// // Remove an existing machine config +// workspace.getConfig().getEnvironments().get(0).getMachineConfigs().remove(1); +// +// // Add a new machine config +// final MachineConfigImpl newMachineCfg = new MachineConfigImpl(); +// newMachineCfg.setName("name3"); +// newMachineCfg.setDev(false); +// newMachineCfg.setType("type3"); +// newMachineCfg.setLimits(new MachineLimitsImpl(2048)); +// newMachineCfg.getEnvVariables().putAll(ImmutableMap.of("env4", "value4", +// "env5", "value5", +// "env6", "value6")); +// newMachineCfg.setServers(new ArrayList<>(singleton(new ServerConfImpl("ref", "port", "protocol", "path")))); +// newMachineCfg.setSource(new MachineSourceImpl("type", "location", "content")); +// workspace.getConfig().getEnvironments().get(0).getMachineConfigs().add(newMachineCfg); +// +// // Update an existing machine configuration +// final MachineConfigImpl machineCfg = workspace.getConfig().getEnvironments().get(0).getMachineConfigs().get(0); +// machineCfg.getEnvVariables().clear(); +// machineCfg.setType("new-type"); +// machineCfg.setName("new-name"); +// machineCfg.getLimits().setRam(512); +// machineCfg.getServers().clear(); +// machineCfg.getServers().add(new ServerConfImpl("new-ref", "new-port", "new-protocol", "new-path")); +// machineCfg.getSource().setType("new-type"); +// machineCfg.getSource().setLocation("new-location"); +// machineCfg.getSource().setContent("new-content"); +// +// // Remove an existing environment +// workspace.getConfig().getEnvironments().remove(1); +// +// // Add a new environment +// final EnvironmentImpl newEnv = new EnvironmentImpl(); +// newEnv.setName("new-env"); +// final MachineConfigImpl newEnvMachineCfg = new MachineConfigImpl(newMachineCfg); +// newEnvMachineCfg.setDev(true); +// newEnv.getMachineConfigs().add(newEnvMachineCfg); +// workspace.getConfig().getEnvironments().add(newEnv); +// +// // Update an existing environment +// final EnvironmentImpl environment = workspace.getConfig().getEnvironments().get(0); +// environment.setName("new-name"); +// +// // Update workspace configuration +// final WorkspaceConfigImpl wCfg = workspace.getConfig(); +// wCfg.setDefaultEnv(newEnv.getName()); +// wCfg.setName("new-name"); +// wCfg.setDescription("This is a new description"); +// +// // Update workspace object +// workspace.setName("new-name"); +// workspace.setAccount(new AccountImpl("accId", "new-namespace", "test")); +// workspace.getAttributes().clear(); +// +// workspaceDao.update(workspace); +// +// assertEquals(workspaceDao.get(workspace.getId()), new WorkspaceImpl(workspace, workspace.getAccount())); + } + + @Test(expectedExceptions = NotFoundException.class) + public void shouldNotUpdateWorkspaceWhichDoesNotExist() throws Exception { + final WorkspaceImpl workspace = workspaces[0]; + workspace.setId("non-existing-workspace"); + + workspaceDao.update(workspace); + } + + @Test(expectedExceptions = ConflictException.class) + public void shouldNotUpdateWorkspaceWithReservedName() throws Exception { + final WorkspaceImpl workspace1 = workspaces[0]; + final WorkspaceImpl workspace2 = workspaces[1]; + + workspace1.setName(workspace2.getName()); + + workspaceDao.update(workspace1); + } + + @Test(expectedExceptions = NullPointerException.class) + public void shouldThrowNpeWhenUpdatingNull() throws Exception { + workspaceDao.update(null); + } + + public static WorkspaceConfigImpl createWorkspaceConfig(String name) { + // Project Sources configuration + final SourceStorageImpl source1 = new SourceStorageImpl(); + source1.setType("type1"); + source1.setLocation("location1"); + source1.setParameters(new HashMap<>(ImmutableMap.of("param1", "value1", + "param2", "value2", + "param3", "value3"))); + final SourceStorageImpl source2 = new SourceStorageImpl(); + source2.setType("type2"); + source2.setLocation("location2"); + source2.setParameters(new HashMap<>(ImmutableMap.of("param4", "value1", + "param5", "value2", + "param6", "value3"))); + + // Project Configuration + final ProjectConfigImpl pCfg1 = new ProjectConfigImpl(); + pCfg1.setPath("/path1"); + pCfg1.setType("type1"); + pCfg1.setName("project1"); + pCfg1.setDescription("description1"); + pCfg1.getMixins().addAll(asList("mixin1", "mixin2")); + pCfg1.setSource(source1); + pCfg1.getAttributes().putAll(ImmutableMap.of("key1", asList("v1", "v2"), "key2", asList("v1", "v2"))); + + final ProjectConfigImpl pCfg2 = new ProjectConfigImpl(); + pCfg2.setPath("/path2"); + pCfg2.setType("type2"); + pCfg2.setName("project2"); + pCfg2.setDescription("description2"); + pCfg2.getMixins().addAll(asList("mixin3", "mixin4")); + pCfg2.setSource(source2); + pCfg2.getAttributes().putAll(ImmutableMap.of("key3", asList("v1", "v2"), "key4", asList("v1", "v2"))); + + final List projects = new ArrayList<>(asList(pCfg1, pCfg2)); + + // Commands + final CommandImpl cmd1 = new CommandImpl("name1", "cmd1", "type1"); + cmd1.getAttributes().putAll(ImmutableMap.of("key1", "value1", + "key2", "value2", + "key3", "value3")); + final CommandImpl cmd2 = new CommandImpl("name2", "cmd2", "type2"); + cmd2.getAttributes().putAll(ImmutableMap.of("key4", "value4", + "key5", "value5", + "key6", "value6")); + final List commands = new ArrayList<>(asList(cmd1, cmd2)); + + // Machine configs + final ExtendedMachineImpl exMachine1 = new ExtendedMachineImpl(); + final ServerConf2Impl serverConf1 = new ServerConf2Impl("2265", "http", singletonMap("prop1", "val")); + final ServerConf2Impl serverConf2 = new ServerConf2Impl("2266", "ftp", singletonMap("prop1", "val")); + exMachine1.setServers(ImmutableMap.of("ref1", serverConf1, "ref2", serverConf2)); + exMachine1.setAgents(ImmutableList.of("agent5", "agent4")); + exMachine1.setAttributes(singletonMap("att1", "val")); + + final ExtendedMachineImpl exMachine2 = new ExtendedMachineImpl(); + final ServerConf2Impl serverConf3 = new ServerConf2Impl("2333", "https", singletonMap("prop2", "val")); + final ServerConf2Impl serverConf4 = new ServerConf2Impl("2334", "wss", singletonMap("prop2", "val")); + exMachine2.setServers(ImmutableMap.of("ref1", serverConf3, "ref2", serverConf4)); + exMachine2.setAgents(ImmutableList.of("agent2", "agent1")); + exMachine2.setAttributes(singletonMap("att1", "val")); + + final ExtendedMachineImpl exMachine3 = new ExtendedMachineImpl(); + final ServerConf2Impl serverConf5 = new ServerConf2Impl("2333", "https", singletonMap("prop2", "val")); + exMachine3.setServers(singletonMap("ref1", serverConf5)); + exMachine3.setAgents(ImmutableList.of("agent6", "agent2")); + exMachine3.setAttributes(singletonMap("att1", "val")); + + + // Environments + final EnvironmentRecipeImpl recipe1 = new EnvironmentRecipeImpl(); + recipe1.setLocation("https://eclipse.che/Dockerfile"); + recipe1.setType("dockerfile"); + recipe1.setContentType("text/x-dockerfile"); + recipe1.setContent("content"); + final EnvironmentImpl env1 = new EnvironmentImpl(); + env1.setMachines(new HashMap<>(ImmutableMap.of("machine1", exMachine1, + "machine2", exMachine2, + "machine3", exMachine3))); + env1.setRecipe(recipe1); + + final EnvironmentRecipeImpl recipe2 = new EnvironmentRecipeImpl(); + recipe2.setLocation("https://eclipse.che/Dockerfile"); + recipe2.setType("dockerfile"); + recipe2.setContentType("text/x-dockerfile"); + recipe2.setContent("content"); + final EnvironmentImpl env2 = new EnvironmentImpl(); + env2.setMachines(new HashMap<>(ImmutableMap.of("machine1", exMachine1, + "machine3", exMachine3))); + env2.setRecipe(recipe2); + + final Map environments = ImmutableMap.of("env1", env1, "env2", env2); + + // Workspace configuration + final WorkspaceConfigImpl wCfg = new WorkspaceConfigImpl(); + wCfg.setDefaultEnv("env1"); + wCfg.setName(name); + wCfg.setDescription("description"); + wCfg.setCommands(commands); + wCfg.setProjects(projects); + wCfg.setEnvironments(new HashMap<>(environments)); + return wCfg; + } + + public static WorkspaceImpl createWorkspace(String id, AccountImpl account, String name) { + final WorkspaceConfigImpl wCfg = createWorkspaceConfig(name); + // Workspace + final WorkspaceImpl workspace = new WorkspaceImpl(); + workspace.setStatus(WorkspaceStatus.STOPPED); + workspace.setId(id); + workspace.setAccount(account); + workspace.setName(name); + workspace.setAttributes(new HashMap<>(ImmutableMap.of("attr1", "value1", + "attr2", "value2", + "attr3", "value3"))); + workspace.setConfig(wCfg); + return workspace; + } +} diff --git a/wsmaster/che-core-api-workspace/src/test/java/org/eclipse/che/api/workspace/server/stack/StackServiceTest.java b/wsmaster/che-core-api-workspace/src/test/java/org/eclipse/che/api/workspace/server/stack/StackServiceTest.java index 8830ca0257..ddf1c4e754 100644 --- a/wsmaster/che-core-api-workspace/src/test/java/org/eclipse/che/api/workspace/server/stack/StackServiceTest.java +++ b/wsmaster/che-core-api-workspace/src/test/java/org/eclipse/che/api/workspace/server/stack/StackServiceTest.java @@ -47,7 +47,6 @@ import org.testng.annotations.Test; import javax.ws.rs.core.UriInfo; import java.io.File; -import java.io.IOException; import java.lang.reflect.Field; import java.net.URISyntaxException; import java.util.List; @@ -132,11 +131,14 @@ public class StackServiceTest { @Mock StackComponentImpl stackComponent; + @Mock + StackValidator validator; + @InjectMocks StackService service; @BeforeMethod - public void setUp() throws IOException, ConflictException { + public void setUp() throws NoSuchFieldException, IllegalAccessException { byte[] fileContent = STACK_ID.getBytes(); stackIcon = new StackIcon(ICON_MEDIA_TYPE, "image/svg+xml", fileContent); componentsImpl = singletonList(new StackComponentImpl(COMPONENT_NAME, COMPONENT_VERSION)); @@ -188,10 +190,7 @@ public class StackServiceTest { .setWorkspaceConfig(workspaceConfig) .setStackIcon(stackIcon) .build(); - } - @BeforeMethod - public void setUpUriInfo() throws NoSuchFieldException, IllegalAccessException { when(uriInfo.getBaseUriBuilder()).thenReturn(new UriBuilderImpl()); final Field uriField = service.getClass() @@ -203,56 +202,8 @@ public class StackServiceTest { /** Create stack */ - @Test - public void shouldThrowBadRequestExceptionWhenUserTryCreateNullStack() { - final Response response = given().auth() - .basic(ADMIN_USER_NAME, ADMIN_USER_PASSWORD) - .contentType(APPLICATION_JSON) - .when() - .post(SECURE_PATH + "/stack"); - - assertEquals(response.getStatusCode(), 400); - assertEquals(unwrapDto(response, ServiceError.class).getMessage(), "Stack required"); - } - - @Test - public void shouldThrowBadRequestExceptionWhenUserTryCreateStackWithNonRequiredStackName() { - stackDto.setName(null); - - final Response response = given().auth() - .basic(ADMIN_USER_NAME, ADMIN_USER_PASSWORD) - .contentType(APPLICATION_JSON) - .body(stackDto) - .when() - .post(SECURE_PATH + "/stack"); - - assertEquals(response.getStatusCode(), 400); - assertEquals(unwrapDto(response, ServiceError.class).getMessage(), "Stack name required"); - } - - @Test - public void shouldThrowBadRequestExceptionWhenUserTryCreateStackWithNonRequiredSource() { - stackDto.setSource(null); - stackDto.setWorkspaceConfig(null); - - final Response response = given().auth() - .basic(ADMIN_USER_NAME, ADMIN_USER_PASSWORD) - .contentType(APPLICATION_JSON) - .body(stackDto) - .when() - .post(SECURE_PATH + "/stack"); - - assertEquals(response.getStatusCode(), 400); - String expectedErrorMessage = "Stack source required. You must specify stack source: 'workspaceConfig' or 'stackSource'"; - assertEquals(unwrapDto(response, ServiceError.class).getMessage(), expectedErrorMessage); - } - @Test public void newStackShouldBeCreatedForUser() throws ConflictException, ServerException { - stackShouldBeCreated(); - } - - private void stackShouldBeCreated() throws ConflictException, ServerException { final Response response = given().auth() .basic(ADMIN_USER_NAME, ADMIN_USER_PASSWORD) .contentType(APPLICATION_JSON) @@ -282,40 +233,40 @@ public class StackServiceTest { assertEquals(stackDtoDescriptor.getLinks().get(1).getRel(), LINK_REL_GET_STACK_BY_ID); } - @Test - public void shouldThrowBadRequestExceptionOnCreateStackWithEmptyBody() { - final Response response = given().auth() - .basic(ADMIN_USER_NAME, ADMIN_USER_PASSWORD) - .contentType(APPLICATION_JSON) - .when() - .post(SECURE_PATH + "/stack"); +// @Test +// public void shouldThrowBadRequestExceptionOnCreateStackWithEmptyBody() { +// final Response response = given().auth() +// .basic(ADMIN_USER_NAME, ADMIN_USER_PASSWORD) +// .contentType(APPLICATION_JSON) +// .when() +// .post(SECURE_PATH + "/stack"); +// +// assertEquals(response.getStatusCode(), 400); +// assertEquals(unwrapDto(response, ServiceError.class).getMessage(), "Stack required"); +// } - assertEquals(response.getStatusCode(), 400); - assertEquals(unwrapDto(response, ServiceError.class).getMessage(), "Stack required"); - } - - @Test - public void shouldThrowBadRequestExceptionOnCreateStackWithEmptyName() { - StackComponentDto stackComponentDto = newDto(StackComponentDto.class).withName("Java").withVersion("1.8.45"); - StackSourceDto stackSourceDto = newDto(StackSourceDto.class).withType("image").withOrigin("codenvy/ubuntu_jdk8"); - StackDto stackDto = newDto(StackDto.class).withId(USER_ID) - .withDescription("") - .withScope("Simple java stack for generation java projects") - .withTags(asList("java", "maven")) - .withCreator("che") - .withComponents(singletonList(stackComponentDto)) - .withSource(stackSourceDto); - - Response response = given().auth() - .basic(ADMIN_USER_NAME, ADMIN_USER_PASSWORD) - .contentType(APPLICATION_JSON) - .body(stackDto) - .when() - .post(SECURE_PATH + "/stack"); - - assertEquals(response.getStatusCode(), 400); - assertEquals(unwrapDto(response, ServiceError.class).getMessage(), "Stack name required"); - } +// @Test +// public void shouldThrowBadRequestExceptionOnCreateStackWithEmptyName() { +// StackComponentDto stackComponentDto = newDto(StackComponentDto.class).withName("Java").withVersion("1.8.45"); +// StackSourceDto stackSourceDto = newDto(StackSourceDto.class).withType("image").withOrigin("codenvy/ubuntu_jdk8"); +// StackDto stackDto = newDto(StackDto.class).withId(USER_ID) +// .withDescription("") +// .withScope("Simple java stack for generation java projects") +// .withTags(asList("java", "maven")) +// .withCreator("che") +// .withComponents(singletonList(stackComponentDto)) +// .withSource(stackSourceDto); +// +// Response response = given().auth() +// .basic(ADMIN_USER_NAME, ADMIN_USER_PASSWORD) +// .contentType(APPLICATION_JSON) +// .body(stackDto) +// .when() +// .post(SECURE_PATH + "/stack"); +// +// assertEquals(response.getStatusCode(), 400); +// assertEquals(unwrapDto(response, ServiceError.class).getMessage(), "Stack name required"); +// } /** Get stack by id */ @@ -343,24 +294,6 @@ public class StackServiceTest { assertEquals(result.getCreator(), stackImpl.getCreator()); } - /** Update stack */ - - @Test - public void shouldThrowBadRequestExceptionWhenUserTryUpdateStackWithNonRequiredSource() { - StackDto updatedStackDto = stackDto.withSource(null).withWorkspaceConfig(null); - - Response response = given().auth() - .basic(ADMIN_USER_NAME, ADMIN_USER_PASSWORD) - .contentType(APPLICATION_JSON) - .content(updatedStackDto) - .when() - .put(SECURE_PATH + "/stack/" + STACK_ID); - - assertEquals(response.getStatusCode(), 400); - String expectedMessage = "Stack source required. You must specify stack source: 'workspaceConfig' or 'stackSource'"; - assertEquals(unwrapDto(response, ServiceError.class).getMessage(), expectedMessage); - } - @Test public void stackShouldBeUpdated() throws NotFoundException, ServerException, ConflictException { final String updatedDescription = "some description"; @@ -409,7 +342,7 @@ public class StackServiceTest { } @Test - public void creatorShouldNotBeUpdated() throws ServerException, NotFoundException { + public void creatorShouldNotBeUpdated() throws ServerException, NotFoundException, ConflictException { StackDto updatedStackDto = DtoFactory.getInstance().createDto(StackDto.class) .withId(STACK_ID) .withName(NAME) @@ -549,7 +482,7 @@ public class StackServiceTest { /** Delete icon by stack id */ @Test - public void stackIconShouldBeDeletedForUserOwner() throws NotFoundException, ServerException { + public void stackIconShouldBeDeletedForUserOwner() throws NotFoundException, ConflictException, ServerException { when(stackDao.getById(stackImpl.getId())).thenReturn(stackImpl); Response response = given().auth() @@ -565,7 +498,7 @@ public class StackServiceTest { /** Update stack icon */ @Test - public void stackIconShouldBeUploadedForUserOwner() throws NotFoundException, ServerException, URISyntaxException { + public void stackIconShouldBeUploadedForUserOwner() throws NotFoundException, ConflictException, ServerException, URISyntaxException { File file = new File(Resources.getResource("stack_img").getPath(), "type-java.svg"); when(stackDao.getById(stackImpl.getId())).thenReturn(stackImpl); @@ -583,7 +516,7 @@ public class StackServiceTest { } @Test - public void foreignStackIconShouldBeUploadedForUser() throws NotFoundException, ServerException { + public void foreignStackIconShouldBeUploadedForUser() throws NotFoundException, ConflictException, ServerException { File file = new File(Resources.getResource("stack_img").getPath(), "type-java.svg"); when(stackDao.getById(foreignStack.getId())).thenReturn(foreignStack); diff --git a/wsmaster/che-core-api-workspace/src/test/java/org/eclipse/che/api/workspace/server/stack/StackValidatorTest.java b/wsmaster/che-core-api-workspace/src/test/java/org/eclipse/che/api/workspace/server/stack/StackValidatorTest.java new file mode 100644 index 0000000000..e684ed629d --- /dev/null +++ b/wsmaster/che-core-api-workspace/src/test/java/org/eclipse/che/api/workspace/server/stack/StackValidatorTest.java @@ -0,0 +1,129 @@ +/******************************************************************************* + * Copyright (c) 2012-2016 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 + *******************************************************************************/ +package org.eclipse.che.api.workspace.server.stack; + +import org.eclipse.che.api.core.BadRequestException; +import org.eclipse.che.api.workspace.shared.dto.WorkspaceConfigDto; +import org.eclipse.che.api.workspace.shared.dto.stack.StackComponentDto; +import org.eclipse.che.api.workspace.shared.dto.stack.StackDto; +import org.eclipse.che.api.workspace.shared.dto.stack.StackSourceDto; +import org.testng.annotations.BeforeMethod; +import org.testng.annotations.Test; + +import java.util.ArrayList; +import java.util.Collections; + +import static org.eclipse.che.dto.server.DtoFactory.newDto; + +/** + * Test for {@link StackValidator} + * + * @author Mihail Kuznyetsov + */ +public class StackValidatorTest { + + StackValidator validator; + + @BeforeMethod + public void setUp() { + validator = new StackValidator(); + } + + @Test + public void shouldcheck() throws Exception { + validator.check(createStack()); + } + + + @Test(expectedExceptions = BadRequestException.class, expectedExceptionsMessageRegExp = "Required non-null stack") + public void shouldNotValidateIfStackIsNull() throws Exception { + validator.check(null); + } + + @Test(expectedExceptions = BadRequestException.class, expectedExceptionsMessageRegExp = "Required non-null stack creator") + public void shouldNotValidateIfStackCreatorIsNull() throws Exception { + validator.check(createStack().withCreator(null)); + } + + @Test(expectedExceptions = BadRequestException.class, expectedExceptionsMessageRegExp = "Required non-null and non-empty stack name") + public void shouldNotValidateIfStackNameIsNull() throws Exception { + validator.check(createStack().withName(null)); + } + + @Test(expectedExceptions = BadRequestException.class, expectedExceptionsMessageRegExp = "Required non-null and non-empty stack name") + public void shouldNotValidateIfStackNameIsEmpty() throws Exception { + validator.check(createStack().withName("")); + } + + @Test + public void shouldValidateIfStackScopeIsGeneral() throws Exception { + validator.check(createStack().withScope("general")); + } + + @Test + public void shouldValidateIfStackScopeIsAdvanced() throws Exception { + validator.check(createStack().withScope("advanced")); + } + + @Test(expectedExceptions = BadRequestException.class, expectedExceptionsMessageRegExp = "Required non-null scope value: 'general' or 'advanced'") + public void shouldNotValidateIfStackScopeIsNull() throws Exception { + validator.check(createStack().withScope(null)); + } + + @Test(expectedExceptions = BadRequestException.class, expectedExceptionsMessageRegExp = "Required non-null scope value: 'general' or 'advanced'") + public void shouldNotValidateIfStackScopeIsNotGeneralOrAdvanced() throws Exception { + validator.check(createStack().withScope("not-valid")); + } + + @Test + public void shouldValidateIfSourceIsWorkspaceConfigAndStackSourceIsNull() throws Exception { + validator.check(createStack().withSource(null)); + } + + @Test + public void shouldValidateIfSourceIsStackSourceAndWorkspaceConfigIsNull() throws Exception { + validator.check(createStack().withWorkspaceConfig(null)); + } + + @Test(expectedExceptions = BadRequestException.class, expectedExceptionsMessageRegExp = "Stack source required. You must specify either 'workspaceConfig' or 'stackSource'") + public void shouldNotValidateIfWorkspaceConfigAndSourceAreNull() throws Exception { + validator.check(createStack().withSource(null).withWorkspaceConfig(null)); + } + + @Test(expectedExceptions = BadRequestException.class, expectedExceptionsMessageRegExp = "Required non-null and non-empty tag list") + public void shouldNotValidateIfStackTagsListIsNull() throws Exception { + validator.check(createStack().withTags(null)); + } + + @Test(expectedExceptions = BadRequestException.class, expectedExceptionsMessageRegExp = "Required non-null and non-empty tag list") + public void shouldNotValidateIfStackTagsListIsEmpty() throws Exception { + validator.check(createStack().withTags(Collections.emptyList())); + } + + @Test + public void shouldNotcheck() throws Exception { + validator.check(createStack()); + } + + private static StackDto createStack() { + return newDto(StackDto.class) + .withId("stack123") + .withName("Name") + .withDescription("Description") + .withScope("general") + .withCreator("user123") + .withTags(new ArrayList<>(Collections.singletonList("latest"))) + .withWorkspaceConfig(newDto(WorkspaceConfigDto.class)) + .withSource(newDto(StackSourceDto.class).withType("recipe").withOrigin("FROM codenvy/ubuntu_jdk8")) + .withComponents(new ArrayList<>( + Collections.singletonList(newDto(StackComponentDto.class).withName("maven").withVersion("3.3.1")))); + } +} diff --git a/wsmaster/che-core-api-workspace/src/test/resources/META-INF/persistence.xml b/wsmaster/che-core-api-workspace/src/test/resources/META-INF/persistence.xml new file mode 100644 index 0000000000..049b389a0a --- /dev/null +++ b/wsmaster/che-core-api-workspace/src/test/resources/META-INF/persistence.xml @@ -0,0 +1,48 @@ + + + + org.eclipse.persistence.jpa.PersistenceProvider + org.eclipse.che.account.spi.AccountImpl + org.eclipse.che.api.workspace.server.model.impl.WorkspaceImpl + org.eclipse.che.api.workspace.server.model.impl.WorkspaceConfigImpl + org.eclipse.che.api.workspace.server.model.impl.ProjectConfigImpl + org.eclipse.che.api.workspace.server.model.impl.EnvironmentImpl + org.eclipse.che.api.workspace.server.model.impl.EnvironmentRecipeImpl + org.eclipse.che.api.workspace.server.model.impl.ExtendedMachineImpl + org.eclipse.che.api.workspace.server.model.impl.ProjectConfigImpl$Attribute + org.eclipse.che.api.workspace.server.model.impl.SourceStorageImpl + org.eclipse.che.api.workspace.server.model.impl.ServerConf2Impl + org.eclipse.che.api.workspace.server.model.impl.stack.StackImpl + org.eclipse.che.api.machine.server.model.impl.CommandImpl + org.eclipse.che.api.machine.server.model.impl.MachineSourceImpl + org.eclipse.che.api.machine.server.model.impl.SnapshotImpl + org.eclipse.che.api.machine.server.recipe.RecipeImpl + true + + + + + + + + + + + + + + + diff --git a/wsmaster/che-core-api-workspace/src/test/resources/META-INF/services/org.eclipse.che.commons.test.tck.TckModule b/wsmaster/che-core-api-workspace/src/test/resources/META-INF/services/org.eclipse.che.commons.test.tck.TckModule new file mode 100644 index 0000000000..91408e3384 --- /dev/null +++ b/wsmaster/che-core-api-workspace/src/test/resources/META-INF/services/org.eclipse.che.commons.test.tck.TckModule @@ -0,0 +1 @@ +org.eclipse.che.api.workspace.server.jpa.WorkspaceTckModule diff --git a/wsmaster/che-core-api-workspace/src/test/resources/stacks.json b/wsmaster/che-core-api-workspace/src/test/resources/stacks.json index 9f9c44ee13..5167e28b3f 100644 --- a/wsmaster/che-core-api-workspace/src/test/resources/stacks.json +++ b/wsmaster/che-core-api-workspace/src/test/resources/stacks.json @@ -62,15 +62,7 @@ } ] }, - "acl": [ - { - "user": "*", - "actions": [ - "search" - ] - } - ], - "stackIcon": { + "stackIcon" : { "name": "type-java.svg", "mediaType": "image/svg+xml" } @@ -153,15 +145,7 @@ "type": "mvn" } ] - }, - "acl": [ - { - "user": "*", - "actions": [ - "search" - ] - } - ] + } }, { "id": "node-default", @@ -241,15 +225,7 @@ "type": "mvn" } ] - }, - "acl": [ - { - "user": "*", - "actions": [ - "search" - ] - } - ] + } }, { "id": "compose-location", @@ -329,15 +305,7 @@ "type": "mvn" } ] - }, - "acl": [ - { - "user": "*", - "actions": [ - "search" - ] - } - ] + } }, { "id": "compose-content", @@ -414,14 +382,6 @@ "type": "mvn" } ] - }, - "acl": [ - { - "user": "*", - "actions": [ - "search" - ] - } - ] + } } ] diff --git a/wsmaster/pom.xml b/wsmaster/pom.xml index ba876a6a72..b6c37c522c 100644 --- a/wsmaster/pom.xml +++ b/wsmaster/pom.xml @@ -19,7 +19,6 @@ 5.0.0-M2-SNAPSHOT ../core/pom.xml - org.eclipse.che.core che-master-parent 5.0.0-M2-SNAPSHOT pom @@ -33,6 +32,7 @@ che-core-api-workspace-shared che-core-api-workspace che-core-api-user-shared + che-core-api-account che-core-api-user che-core-api-factory-shared che-core-api-factory diff --git a/wsmaster/wsmaster-local/pom.xml b/wsmaster/wsmaster-local/pom.xml index 46f815a457..34c3edb332 100644 --- a/wsmaster/wsmaster-local/pom.xml +++ b/wsmaster/wsmaster-local/pom.xml @@ -24,6 +24,22 @@ false + + com.fasterxml.jackson.core + jackson-annotations + + + com.fasterxml.jackson.core + jackson-core + + + com.fasterxml.jackson.core + jackson-databind + + + com.fasterxml.jackson.dataformat + jackson-dataformat-yaml + com.google.code.gson gson @@ -36,6 +52,18 @@ com.google.inject guice + + com.google.inject.extensions + guice-persist + + + com.jcraft + jsch + + + commons-fileupload + commons-fileupload + commons-io commons-io @@ -52,10 +80,26 @@ javax.inject javax.inject + + javax.validation + validation-api + javax.ws.rs javax.ws.rs-api + + org.eclipse.che.core + che-core-api-account + + + org.eclipse.che.core + che-core-api-agent + + + org.eclipse.che.core + che-core-api-agent-shared + org.eclipse.che.core che-core-api-core @@ -64,10 +108,18 @@ org.eclipse.che.core che-core-api-dto + + org.eclipse.che.core + che-core-api-jdbc + org.eclipse.che.core che-core-api-machine + + org.eclipse.che.core + che-core-api-machine-shared + org.eclipse.che.core che-core-api-model @@ -76,6 +128,10 @@ org.eclipse.che.core che-core-api-ssh + + org.eclipse.che.core + che-core-api-ssh-shared + org.eclipse.che.core che-core-api-user @@ -96,10 +152,30 @@ org.eclipse.che.core che-core-commons-annotations + + org.eclipse.che.core + che-core-commons-inject + + + org.eclipse.che.core + che-core-commons-lang + + + org.eclipse.persistence + eclipselink + + + org.eclipse.persistence + javax.persistence + org.everrest everrest-core + + org.everrest + everrest-websockets + org.slf4j slf4j-api @@ -114,6 +190,18 @@ logback-classic test + + org.eclipse.che.core + che-core-api-machine + tests + test + + + org.eclipse.che.core + che-core-api-ssh + tests + test + org.eclipse.che.core che-core-api-user @@ -122,7 +210,8 @@ org.eclipse.che.core - che-core-commons-lang + che-core-api-workspace + tests test @@ -168,7 +257,10 @@ ${project.build.testOutputDirectory} - che-core-api-user + che-core-api-user, + che-core-api-machine, + che-core-api-workspace, + che-core-api-ssh test diff --git a/wsmaster/wsmaster-local/src/main/java/org/eclipse/che/api/local/LocalDataMigrator.java b/wsmaster/wsmaster-local/src/main/java/org/eclipse/che/api/local/LocalDataMigrator.java new file mode 100644 index 0000000000..fca166c185 --- /dev/null +++ b/wsmaster/wsmaster-local/src/main/java/org/eclipse/che/api/local/LocalDataMigrator.java @@ -0,0 +1,458 @@ +/******************************************************************************* + * Copyright (c) 2012-2016 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 + *******************************************************************************/ +package org.eclipse.che.api.local; + +import com.google.common.collect.ImmutableMap; +import com.google.common.reflect.TypeToken; +import com.google.inject.Inject; +import com.google.inject.Singleton; + +import org.eclipse.che.api.core.NotFoundException; +import org.eclipse.che.api.core.model.machine.Recipe; +import org.eclipse.che.api.core.model.project.ProjectConfig; +import org.eclipse.che.api.local.storage.LocalStorage; +import org.eclipse.che.api.local.storage.LocalStorageFactory; +import org.eclipse.che.api.local.storage.stack.StackLocalStorage; +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.recipe.adapters.RecipeTypeAdapter; +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.model.impl.SshPairImpl; +import org.eclipse.che.api.ssh.server.spi.SshDao; +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.WorkspaceConfigJsonAdapter; +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.api.workspace.server.stack.StackJsonAdapter; +import org.eclipse.che.commons.lang.Pair; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import javax.annotation.PostConstruct; +import javax.inject.Named; +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.time.LocalDateTime; +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.concurrent.Callable; +import java.util.stream.Collectors; + +import static java.lang.System.currentTimeMillis; +import static java.util.Collections.singletonMap; + +/** + * The component which migrates all the local data to different storage. + * If it fails it will throw an appropriate exception and container start will be terminated. + * If the migration is terminated it will continue migration from the fail point. + * + *

The migration strategy(for one entity type) + *

    + *
  • Load all the entity instances + *
  • For each entity instance check whether such entity exists in the jpa based storage. + * There is no need to check by anything else except of identifier. + *
  • If an entity with such identifier exists then it is already migrated, otherwise + * save the entity. + *
  • If an error occurred during the entity saving saveStacks the migration and populate the error + *
+ * + * @author Yevhenii Voevodin + */ +@Singleton +public class LocalDataMigrator { + + private static final Logger LOG = LoggerFactory.getLogger(LocalDataMigrator.class); + + @Inject + @PostConstruct + public void performMigration(@Named("che.conf.storage") String baseDir, + UserDao userDao, + ProfileDao profileDao, + PreferenceDao preferenceDao, + SshDao sshDao, + WorkspaceDao workspaceDao, + SnapshotDao snapshotDao, + RecipeDao recipeDao, + StackDao stackDao, + StackJsonAdapter stackJsonAdapter, + WorkspaceConfigJsonAdapter cfgAdapter) throws Exception { + final LocalStorageFactory factory = new LocalStorageFactory(baseDir); + + // Create all the objects needed for migration, the order is important + final List> migrations = new ArrayList<>(); + final Map, Object> adapters = ImmutableMap.of(WorkspaceImpl.class, new WorkspaceDeserializer(), + Recipe.class, new RecipeTypeAdapter(), + ProjectConfig.class, new ProjectConfigAdapter(), + WorkspaceConfigImpl.class, new WorkspaceConfigDeserializer(cfgAdapter)); + migrations.add(new UserMigration(factory.create(LocalUserDaoImpl.FILENAME), userDao)); + migrations.add(new ProfileMigration(factory.create(LocalProfileDaoImpl.FILENAME), profileDao)); + migrations.add(new PreferencesMigration(factory.create(LocalPreferenceDaoImpl.FILENAME), preferenceDao)); + migrations.add(new SshKeyMigration(factory.create(LocalSshDaoImpl.FILENAME), sshDao)); + migrations.add(new WorkspaceMigration(factory.create(LocalWorkspaceDaoImpl.FILENAME, adapters), workspaceDao, userDao)); + migrations.add(new SnapshotMigration(factory.create(LocalSnapshotDaoImpl.FILENAME), snapshotDao)); + migrations.add(new RecipeMigration(factory.create(LocalRecipeDaoImpl.FILENAME), recipeDao)); + migrations.add(new StackMigration(factory.create(StackLocalStorage.STACK_STORAGE_FILE, + singletonMap(StackImpl.class, + new StackDeserializer(stackJsonAdapter))), stackDao)); + + long globalMigrationStart = -1; + + for (Migration migration : migrations) { + // If there is no file, then migration for this entity type is already done, skip it + if (!Files.exists(migration.getPath())) continue; + + // Inform about the general migration start, if not informed + if (globalMigrationStart == -1) { + globalMigrationStart = currentTimeMillis(); + LOG.info("Components migration started", LocalDateTime.now()); + } + + // Migrate entities + LOG.info("Starting migration of '{}' entities", migration.getEntityName()); + final long migrationStart = currentTimeMillis(); + final int migrated = migrateAll(migration); + LOG.info("Migration of '{}' entities successfully finished. Migration time: {}ms, Migrated count: {}, Skipped count: {}", + migration.getEntityName(), + currentTimeMillis() - migrationStart, + migrated, + migration.getAllEntities().size() - migrated); + + // Backup the file, and remove the original one to avoid future migrations + // e.g. /storage/users.json becomes /storage/users.json.backup + final Path dataFile = migration.getPath(); + try { + Files.move(dataFile, dataFile.resolveSibling(dataFile.getFileName().toString() + ".backup")); + } catch (IOException x) { + LOG.error("Couldn't move {} to {}.backup due to an error. Error: {}", + dataFile.toString(), + dataFile.toString(), + x.getLocalizedMessage()); + throw x; + } + } + + if (globalMigrationStart != -1) { + LOG.info("Components migration successfully finished. Total migration time: {}ms", currentTimeMillis() - globalMigrationStart); + } + } + + /** + * Migrates entities and skips those which are already migrated. + * + * @param migration + * the migration + * @param + * the type of the migration + * @return the count of migrated entities + * @throws Exception + * when any error occurs + */ + private static int migrateAll(Migration migration) throws Exception { + int migrated = 0; + for (T entity : migration.getAllEntities()) { + // Skip those entities which are already migrated. + // e.g. this check allows migration to fail and then continue from failed point + try { + if (migration.isMigrated(entity)) continue; + } catch (Exception x) { + LOG.error("Couldn't check if the entity '{}' is migrated due to occurred error", entity); + throw x; + } + + // The entity is not migrated, so migrate it + try { + migration.migrate(entity); + } catch (Exception x) { + LOG.error("Error migrating the entity '{}", entity); + throw x; + } + migrated++; + } + return migrated; + } + + /** + * The base class for all migrations. + * + * @param + * the type of the entities migrated by this migration + */ + public static abstract class Migration { + protected final String entityName; + protected final LocalStorage storage; + + public Migration(String entityName, LocalStorage localStorage) { + this.entityName = entityName; + this.storage = localStorage; + } + + public String getEntityName() { + return entityName; + } + + public Path getPath() { + return storage.getFile().toPath(); + } + + public abstract List getAllEntities() throws Exception; + + public abstract void migrate(T entity) throws Exception; + + public abstract boolean isMigrated(T entity) throws Exception; + } + + public static class UserMigration extends Migration { + private final UserDao userDao; + + public UserMigration(LocalStorage localStorage, UserDao userDao) { + super("User", localStorage); + this.userDao = userDao; + } + + @Override + public List getAllEntities() { + return new ArrayList<>(storage.loadMap(new TypeToken>() {}).values()); + } + + @Override + public void migrate(UserImpl entity) throws Exception { + userDao.create(entity); + } + + @Override + public boolean isMigrated(UserImpl entity) throws Exception { + return exists(() -> userDao.getById(entity.getId())); + } + } + + public static class ProfileMigration extends Migration { + private final ProfileDao profileDao; + + public ProfileMigration(LocalStorage localStorage, ProfileDao profileDao) { + super("Profile", localStorage); + this.profileDao = profileDao; + } + + @Override + public List getAllEntities() throws Exception { + final Collection profiles = storage.loadMap(new TypeToken>() {}).values(); + profiles.stream() + .filter(profile -> profile.id != null) + .forEach(profile -> profile.setUserId(profile.id)); + return new ArrayList<>(profiles); + } + + @Override + public void migrate(ProfileImpl entity) throws Exception { + profileDao.create(new ProfileImpl(entity)); + } + + @Override + public boolean isMigrated(ProfileImpl entity) throws Exception { + return exists(() -> profileDao.getById(entity.getUserId())); + } + + private static class OldProfile extends ProfileImpl { + public String id; + } + } + + public static class PreferencesMigration extends Migration>> { + + private final PreferenceDao preferenceDao; + + public PreferencesMigration(LocalStorage localStorage, PreferenceDao preferenceDao) { + super("Preferences", localStorage); + this.preferenceDao = preferenceDao; + } + + @Override + public List>> getAllEntities() throws Exception { + return storage.loadMap(new TypeToken>>() {}) + .entrySet() + .stream() + .map(e -> Pair.of(e.getKey(), e.getValue())) + .collect(Collectors.toList()); + } + + @Override + public void migrate(Pair> entity) throws Exception { + if ("codenvy".equals(entity.first)) { + preferenceDao.setPreferences("che", entity.second); + } else { + preferenceDao.setPreferences(entity.first, entity.second); + } + } + + @Override + public boolean isMigrated(Pair> entity) throws Exception { + return !preferenceDao.getPreferences(entity.first).isEmpty(); + } + } + + public static class SshKeyMigration extends Migration { + + private final SshDao sshDao; + + public SshKeyMigration(LocalStorage localStorage, SshDao sshDao) { + super("SshKeyPair", localStorage); + this.sshDao = sshDao; + } + + @Override + public List getAllEntities() throws Exception { + Set>> entries = storage.loadMap(new TypeToken>>() {}) + .entrySet(); + + final List result = new ArrayList<>(); + for (Map.Entry> entry : entries) { + result.addAll(entry.getValue() + .stream() + .map(v -> new SshPairImpl(entry.getKey(), v)).collect(Collectors.toList())); + } + return result; + } + + @Override + public void migrate(SshPairImpl entity) throws Exception { + sshDao.create(entity); + } + + @Override + public boolean isMigrated(SshPairImpl entity) throws Exception { + return exists(() -> sshDao.get(entity.getOwner(), entity.getService(), entity.getName())); + } + } + + public static class WorkspaceMigration extends Migration { + + private final WorkspaceDao workspaceDao; + private final UserDao userDao; + + public WorkspaceMigration(LocalStorage localStorage, WorkspaceDao workspaceDao, UserDao userDao) { + super("Workspace", localStorage); + this.workspaceDao = workspaceDao; + this.userDao = userDao; + } + + @Override + public List getAllEntities() throws Exception { + return new ArrayList<>(storage.loadMap(new TypeToken>() {}).values()); + } + + @Override + public void migrate(WorkspaceImpl entity) throws Exception { + entity.setAccount(userDao.getByName(entity.getNamespace()).getAccount()); + entity.setName(entity.getConfig().getName()); + workspaceDao.create(entity); + } + + @Override + public boolean isMigrated(WorkspaceImpl entity) throws Exception { + return exists(() -> workspaceDao.get(entity.getId())); + } + } + + public static class SnapshotMigration extends Migration { + private final SnapshotDao snapshotDao; + + public SnapshotMigration(LocalStorage localStorage, SnapshotDao snapshotDao) { + super("Snapshot", localStorage); + this.snapshotDao = snapshotDao; + } + + @Override + public List getAllEntities() throws Exception { + return new ArrayList<>(storage.loadMap(new TypeToken>() {}).values()); + } + + @Override + public void migrate(SnapshotImpl entity) throws Exception { + snapshotDao.saveSnapshot(entity); + } + + @Override + public boolean isMigrated(SnapshotImpl entity) throws Exception { + return exists(() -> snapshotDao.getSnapshot(entity.getId())); + } + } + + public static class RecipeMigration extends Migration { + + private final RecipeDao recipeDao; + + public RecipeMigration(LocalStorage localStorage, RecipeDao recipeDao) { + super("Recipe", localStorage); + this.recipeDao = recipeDao; + } + + @Override + public List getAllEntities() throws Exception { + return new ArrayList<>(storage.loadMap(new TypeToken>() {}).values()); + } + + @Override + public void migrate(RecipeImpl entity) throws Exception { + recipeDao.create(entity); + } + + @Override + public boolean isMigrated(RecipeImpl entity) throws Exception { + return exists(() -> recipeDao.getById(entity.getId())); + } + } + + public static class StackMigration extends Migration { + + private final StackDao stackDao; + + public StackMigration(LocalStorage localStorage, StackDao stackDao) { + super("Stack", localStorage); + this.stackDao = stackDao; + } + + @Override + public List getAllEntities() throws Exception { + return new ArrayList<>(storage.loadMap(new TypeToken>() {}).values()); + } + + @Override + public void migrate(StackImpl entity) throws Exception { + stackDao.create(entity); + } + + @Override + public boolean isMigrated(StackImpl entity) throws Exception { + return exists(() -> stackDao.getById(entity.getId())); + } + } + + public static boolean exists(Callable action) throws Exception { + try { + action.call(); + } catch (NotFoundException x) { + return false; + } + return true; + } +} diff --git a/wsmaster/wsmaster-local/src/main/java/org/eclipse/che/api/local/LocalInfrastructureModule.java b/wsmaster/wsmaster-local/src/main/java/org/eclipse/che/api/local/LocalInfrastructureModule.java index cf8d826c54..71e642824f 100644 --- a/wsmaster/wsmaster-local/src/main/java/org/eclipse/che/api/local/LocalInfrastructureModule.java +++ b/wsmaster/wsmaster-local/src/main/java/org/eclipse/che/api/local/LocalInfrastructureModule.java @@ -11,25 +11,18 @@ package org.eclipse.che.api.local; import com.google.inject.AbstractModule; -import com.google.inject.Provides; import org.eclipse.che.api.local.storage.LocalStorageFactory; -import org.eclipse.che.api.machine.server.dao.RecipeDao; -import org.eclipse.che.api.machine.server.dao.SnapshotDao; +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.spi.SshDao; import org.eclipse.che.api.user.server.TokenValidator; -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.spi.StackDao; import org.eclipse.che.api.workspace.server.spi.WorkspaceDao; -import javax.inject.Named; -import java.util.Collections; -import java.util.HashSet; -import java.util.Set; - public class LocalInfrastructureModule extends AbstractModule { @Override protected void configure() { @@ -44,17 +37,4 @@ public class LocalInfrastructureModule extends AbstractModule { bind(StackDao.class).to(LocalStackDaoImpl.class); bind(LocalStorageFactory.class); } - - @Provides - @Named("codenvy.local.infrastructure.users") - Set users() { - final Set users = new HashSet<>(); - final UserImpl user = new UserImpl("che", - "che@eclipse.org", - "che", - "secret", - Collections.emptyList()); - users.add(user); - return users; - } } diff --git a/wsmaster/wsmaster-local/src/main/java/org/eclipse/che/api/local/LocalPreferenceDaoImpl.java b/wsmaster/wsmaster-local/src/main/java/org/eclipse/che/api/local/LocalPreferenceDaoImpl.java index 775de4c741..fa1a8484fe 100644 --- a/wsmaster/wsmaster-local/src/main/java/org/eclipse/che/api/local/LocalPreferenceDaoImpl.java +++ b/wsmaster/wsmaster-local/src/main/java/org/eclipse/che/api/local/LocalPreferenceDaoImpl.java @@ -11,6 +11,7 @@ package org.eclipse.che.api.local; +import com.google.common.annotations.VisibleForTesting; import com.google.common.reflect.TypeToken; import org.eclipse.che.api.core.ServerException; @@ -27,12 +28,17 @@ import javax.inject.Singleton; import java.io.IOException; import java.util.HashMap; import java.util.Map; -import java.util.concurrent.locks.ReadWriteLock; -import java.util.concurrent.locks.ReentrantReadWriteLock; import java.util.regex.Pattern; +import static java.util.Objects.requireNonNull; + /** - * @author Eugene Voevodin + * In-memory implementation of {@link PreferenceDao}. + * + *

The implementation is thread-safe guarded by this instance. + * Clients may use instance locking to perform extra, thread-safe operation. + * + * @author Yevhenii Voevodin * @author Dmitry Shnurenko * @author Anton Korneta * @author Valeriy Svydenko @@ -40,21 +46,23 @@ import java.util.regex.Pattern; @Singleton public class LocalPreferenceDaoImpl implements PreferenceDao { + public static final String FILENAME = "preferences.json"; + private static final Logger LOG = LoggerFactory.getLogger(LocalPreferenceDaoImpl.class); - private final Map> preferences; - private final ReadWriteLock lock; - private final LocalStorage preferenceStorage; + private final LocalStorage preferenceStorage; + + @VisibleForTesting + final Map> preferences; @Inject public LocalPreferenceDaoImpl(LocalStorageFactory localStorageFactory) throws IOException { preferences = new HashMap<>(); - lock = new ReentrantReadWriteLock(); preferenceStorage = localStorageFactory.create("preferences.json"); } @PostConstruct - private void start() { + private synchronized void start() { preferences.putAll(preferenceStorage.loadMap(new TypeToken>>() {})); // Add default entry if file doesn't exist or invalid or empty. if (preferences.isEmpty()) { @@ -65,53 +73,53 @@ public class LocalPreferenceDaoImpl implements PreferenceDao { } } - @PreDestroy - private void stop() throws IOException { + public synchronized void savePreferences() throws IOException { preferenceStorage.store(preferences); } @Override - public void setPreferences(String userId, Map prefs) throws ServerException { - lock.writeLock().lock(); + public synchronized void setPreferences(String userId, Map prefs) throws ServerException { + requireNonNull(userId); + requireNonNull(prefs); try { preferences.put(userId, new HashMap<>(prefs)); preferenceStorage.store(preferences); } catch (IOException e) { LOG.warn("Impossible to store preferences"); - } finally { - lock.writeLock().unlock(); } } @Override - public Map getPreferences(String userId) throws ServerException { - lock.readLock().lock(); - try { - //Need read all new preferences without restarting dev-machine. It is needed for IDEX-2180 - preferences.putAll(preferenceStorage.loadMap(new TypeToken>>() {})); - final Map prefs = new HashMap<>(); - if (preferences.containsKey(userId)) { - prefs.putAll(preferences.get(userId)); - } - return prefs; - } finally { - lock.readLock().unlock(); + public synchronized Map getPreferences(String userId) throws ServerException { + requireNonNull(userId); + //Need read all new preferences without restarting dev-machine. It is needed for IDEX-2180 + preferences.putAll(preferenceStorage.loadMap(new TypeToken>>() {})); + final Map prefs = new HashMap<>(); + if (preferences.containsKey(userId)) { + prefs.putAll(preferences.get(userId)); } + return prefs; } @Override - public Map getPreferences(String userId, String filter) throws ServerException { - lock.readLock().lock(); - try { - return filter(getPreferences(userId), filter); - } finally { - lock.readLock().unlock(); - } + public synchronized Map getPreferences(String userId, String filter) throws ServerException { + requireNonNull(userId); + requireNonNull(filter); + return filter(getPreferences(userId), filter); + } + + @Override + public synchronized void remove(String userId) throws ServerException { + requireNonNull(userId); + preferences.remove(userId); } private Map filter(Map prefs, String filter) { final Map filtered = new HashMap<>(); final Pattern pattern = Pattern.compile(filter); + if (filter.isEmpty()) { + return prefs; + } for (Map.Entry entry : prefs.entrySet()) { if (pattern.matcher(entry.getKey()).matches()) { filtered.put(entry.getKey(), entry.getValue()); @@ -119,14 +127,4 @@ public class LocalPreferenceDaoImpl implements PreferenceDao { } return filtered; } - - @Override - public void remove(String userId) throws ServerException { - lock.writeLock().lock(); - try { - preferences.remove(userId); - } finally { - lock.writeLock().unlock(); - } - } } diff --git a/wsmaster/wsmaster-local/src/main/java/org/eclipse/che/api/local/LocalProfileDaoImpl.java b/wsmaster/wsmaster-local/src/main/java/org/eclipse/che/api/local/LocalProfileDaoImpl.java index 0325f50c87..def4c6e60f 100644 --- a/wsmaster/wsmaster-local/src/main/java/org/eclipse/che/api/local/LocalProfileDaoImpl.java +++ b/wsmaster/wsmaster-local/src/main/java/org/eclipse/che/api/local/LocalProfileDaoImpl.java @@ -21,7 +21,6 @@ import org.eclipse.che.api.local.storage.LocalStorageFactory; import org.eclipse.che.api.core.model.user.Profile; import org.eclipse.che.api.user.server.model.impl.ProfileImpl; import org.eclipse.che.api.user.server.spi.ProfileDao; -import org.eclipse.che.api.user.server.spi.UserDao; import javax.annotation.PostConstruct; import javax.annotation.PreDestroy; @@ -31,33 +30,36 @@ import java.io.IOException; import java.util.HashMap; import java.util.LinkedHashMap; import java.util.Map; -import java.util.concurrent.locks.ReadWriteLock; -import java.util.concurrent.locks.ReentrantReadWriteLock; import static java.lang.String.format; import static java.util.Objects.requireNonNull; /** + * In-memory implementation of {@link ProfileDao}. + * + *

The implementation is thread-safe guarded by this instance. + * Clients may use instance locking to perform extra, thread-safe operation. + * * @author Anton Korneta */ @Singleton public class LocalProfileDaoImpl implements ProfileDao { + public static final java.lang.String FILENAME = "profiles.json"; + @VisibleForTesting final Map profiles; - private final ReadWriteLock lock; - private final LocalStorage profileStorage; + private final LocalStorage profileStorage; @Inject public LocalProfileDaoImpl(LocalStorageFactory storageFactory) throws IOException { profiles = new LinkedHashMap<>(); - lock = new ReentrantReadWriteLock(); - profileStorage = storageFactory.create("profiles.json"); + profileStorage = storageFactory.create(FILENAME); } @PostConstruct - private void start() { + private synchronized void start() { profiles.putAll(profileStorage.loadMap(new TypeToken>() {})); // Add default entry if file doesn't exist or invalid or empty. if (profiles.isEmpty()) { @@ -70,64 +72,43 @@ public class LocalProfileDaoImpl implements ProfileDao { } } - @PreDestroy - private void stop() throws IOException { + public synchronized void saveProfiles() throws IOException { profileStorage.store(profiles); } @Override - public void create(ProfileImpl profile) throws ConflictException { + public synchronized void create(ProfileImpl profile) throws ConflictException { requireNonNull(profile, "Required non-null profile"); - lock.writeLock().lock(); - try { - if (profiles.containsKey(profile.getUserId())) { - throw new ConflictException(format("Profile for user '%s' already exists", profile.getUserId())); - } - profiles.put(profile.getUserId(), new ProfileImpl(profile)); - } finally { - lock.writeLock().unlock(); + if (profiles.containsKey(profile.getUserId())) { + throw new ConflictException(format("Profile for user '%s' already exists", profile.getUserId())); } + profiles.put(profile.getUserId(), new ProfileImpl(profile)); } @Override - public void update(ProfileImpl profile) throws NotFoundException { + public synchronized void update(ProfileImpl profile) throws NotFoundException { requireNonNull(profile, "Required non-null profile"); - lock.writeLock().lock(); - try { - final Profile myProfile = profiles.get(profile.getUserId()); - if (myProfile == null) { - throw new NotFoundException(format("Profile with id '%s' not found", profile.getUserId())); - } - myProfile.getAttributes().clear(); - myProfile.getAttributes().putAll(profile.getAttributes()); - } finally { - lock.writeLock().unlock(); + final Profile myProfile = profiles.get(profile.getUserId()); + if (myProfile == null) { + throw new NotFoundException(format("Profile with id '%s' not found", profile.getUserId())); } + myProfile.getAttributes().clear(); + myProfile.getAttributes().putAll(profile.getAttributes()); } @Override - public void remove(String id) { + public synchronized void remove(String id) { requireNonNull(id, "Required non-null id"); - lock.writeLock().lock(); - try { - profiles.remove(id); - } finally { - lock.writeLock().unlock(); - } + profiles.remove(id); } @Override - public ProfileImpl getById(String id) throws NotFoundException { + public synchronized ProfileImpl getById(String id) throws NotFoundException { requireNonNull(id, "Required non-null id"); - lock.readLock().lock(); - try { - final Profile profile = profiles.get(id); - if (profile == null) { - throw new NotFoundException(format("Profile with id '%s' not found", id)); - } - return new ProfileImpl(profile); - } finally { - lock.readLock().unlock(); + final Profile profile = profiles.get(id); + if (profile == null) { + throw new NotFoundException(format("Profile with id '%s' not found", id)); } + return new ProfileImpl(profile); } } diff --git a/wsmaster/wsmaster-local/src/main/java/org/eclipse/che/api/local/LocalRecipeDaoImpl.java b/wsmaster/wsmaster-local/src/main/java/org/eclipse/che/api/local/LocalRecipeDaoImpl.java index a11fe964ce..02cf2c0790 100644 --- a/wsmaster/wsmaster-local/src/main/java/org/eclipse/che/api/local/LocalRecipeDaoImpl.java +++ b/wsmaster/wsmaster-local/src/main/java/org/eclipse/che/api/local/LocalRecipeDaoImpl.java @@ -10,6 +10,7 @@ *******************************************************************************/ package org.eclipse.che.api.local; +import com.google.common.annotations.VisibleForTesting; import com.google.common.reflect.TypeToken; import com.google.inject.Inject; @@ -18,8 +19,8 @@ import org.eclipse.che.api.core.NotFoundException; import org.eclipse.che.api.core.ServerException; import org.eclipse.che.api.local.storage.LocalStorage; import org.eclipse.che.api.local.storage.LocalStorageFactory; -import org.eclipse.che.api.machine.server.dao.RecipeDao; import org.eclipse.che.api.machine.server.recipe.RecipeImpl; +import org.eclipse.che.api.machine.server.spi.RecipeDao; import javax.annotation.PostConstruct; import javax.annotation.PreDestroy; @@ -28,126 +29,109 @@ import java.io.IOException; import java.util.HashMap; import java.util.List; import java.util.Map; -import java.util.concurrent.locks.ReadWriteLock; -import java.util.concurrent.locks.ReentrantReadWriteLock; import java.util.stream.Collectors; import java.util.stream.Stream; import static java.lang.String.format; +import static java.util.Objects.requireNonNull; /** - * @author Eugene Voevodin + * In-memory implementation of {@link RecipeDao}. + * + *

The implementation is thread-safe guarded by this instance. + * Clients may use instance locking to perform extra, thread-safe operation. + * + * @author Yevhenii Voevodin * @author Anton Korneta */ @Singleton public class LocalRecipeDaoImpl implements RecipeDao { - private final Map recipes; - private final ReadWriteLock lock; - private final LocalStorage recipeStorage; + public static final String FILENAME = "recipes.json"; + + @VisibleForTesting + final Map recipes; + + private final LocalStorage recipeStorage; @Inject public LocalRecipeDaoImpl(LocalStorageFactory storageFactory) throws IOException { - this.recipeStorage = storageFactory.create("recipes.json"); + this.recipeStorage = storageFactory.create(FILENAME); this.recipes = new HashMap<>(); - this.lock = new ReentrantReadWriteLock(); } @PostConstruct - public void loadRecipes() { + public synchronized void loadRecipes() { recipes.putAll(recipeStorage.loadMap(new TypeToken>() {})); } - @PreDestroy - public void saveRecipes() throws IOException { + public synchronized void saveRecipes() throws IOException { recipeStorage.store(recipes); } @Override - public void create(RecipeImpl recipe) throws ConflictException { - lock.writeLock().lock(); - try { - if (recipes.containsKey(recipe.getId())) { - throw new ConflictException(format("Recipe with id %s already exists", recipe.getId())); - } - recipes.put(recipe.getId(), recipe); - } finally { - lock.writeLock().unlock(); + public synchronized void create(RecipeImpl recipe) throws ConflictException { + if (recipes.containsKey(recipe.getId())) { + throw new ConflictException(format("Recipe with id %s already exists", recipe.getId())); } + recipes.put(recipe.getId(), recipe); } @Override - public RecipeImpl update(RecipeImpl update) throws NotFoundException { - lock.writeLock().lock(); - try { - final RecipeImpl target = recipes.get(update.getId()); - if (target == null) { - throw new NotFoundException(format("Recipe with id '%s' was not found", update.getId())); - } - if (update.getType() != null) { - target.setType(update.getType()); - } - if (update.getScript() != null) { - target.setScript(update.getScript()); - } - if (update.getDescription() != null) { - target.setDescription(update.getDescription()); - } - if (update.getName() != null) { - target.setName(update.getName()); - } - if (!update.getTags().isEmpty()) { - target.setTags(update.getTags()); - } - if (update.getAcl() != null && !update.getAcl().isEmpty()) { - target.setAcl(update.getAcl()); - } - - return new RecipeImpl(target); - } finally { - lock.writeLock().unlock(); + public synchronized RecipeImpl update(RecipeImpl update) throws NotFoundException { + final RecipeImpl target = recipes.get(update.getId()); + if (target == null) { + throw new NotFoundException(format("Recipe with id '%s' was not found", update.getId())); } + if (update.getType() != null) { + target.setType(update.getType()); + } + if (update.getScript() != null) { + target.setScript(update.getScript()); + } + if (update.getCreator() != null) { + target.setCreator(update.getCreator()); + } + if (update.getDescription() != null) { + target.setDescription(update.getDescription()); + } + if (update.getName() != null) { + target.setName(update.getName()); + } + if (!update.getTags().isEmpty()) { + target.setTags(update.getTags()); + } + + return new RecipeImpl(target); } @Override - public void remove(String id) { - lock.writeLock().lock(); - try { - recipes.remove(id); - } finally { - lock.writeLock().unlock(); - } + public synchronized void remove(String id) { + requireNonNull(id); + recipes.remove(id); } @Override - public RecipeImpl getById(String id) throws NotFoundException { - lock.readLock().lock(); - try { - final RecipeImpl recipe = recipes.get(id); - if (recipe == null) { - throw new NotFoundException(format("Recipe with id %s was not found", id)); - } - return new RecipeImpl(recipe); - } finally { - lock.readLock().unlock(); + public synchronized RecipeImpl getById(String id) throws NotFoundException { + requireNonNull(id); + final RecipeImpl recipe = recipes.get(id); + if (recipe == null) { + throw new NotFoundException(format("Recipe with id %s was not found", id)); } + return new RecipeImpl(recipe); } @Override - public List search(String user, List tags, String type, int skipCount, int maxItems) throws ServerException { - lock.readLock().lock(); - try { - Stream recipesStream = recipes.values() - .stream() - .filter(recipe -> (tags == null || recipe.getTags().containsAll(tags)) - && (type == null || type.equals(recipe.getType()))) - .skip(skipCount); - if (maxItems != 0) { - recipesStream = recipesStream.limit(maxItems); - } - return recipesStream.collect(Collectors.toList()); - } finally { - lock.readLock().unlock(); + public synchronized List search(String user, List tags, String type, int skipCount, int maxItems) + throws ServerException { + Stream recipesStream = recipes.values() + .stream() + .filter(recipe -> (tags == null || recipe.getTags().containsAll(tags)) + && (type == null || type.equals(recipe.getType()))) + .skip(skipCount); + if (maxItems != 0) { + recipesStream = recipesStream.limit(maxItems); } + return recipesStream.collect(Collectors.toList()); } } diff --git a/wsmaster/wsmaster-local/src/main/java/org/eclipse/che/api/local/LocalSnapshotDaoImpl.java b/wsmaster/wsmaster-local/src/main/java/org/eclipse/che/api/local/LocalSnapshotDaoImpl.java index 23c5d79d9b..925763f929 100644 --- a/wsmaster/wsmaster-local/src/main/java/org/eclipse/che/api/local/LocalSnapshotDaoImpl.java +++ b/wsmaster/wsmaster-local/src/main/java/org/eclipse/che/api/local/LocalSnapshotDaoImpl.java @@ -10,13 +10,14 @@ *******************************************************************************/ package org.eclipse.che.api.local; +import com.google.common.annotations.VisibleForTesting; import com.google.common.reflect.TypeToken; import org.eclipse.che.api.core.NotFoundException; import org.eclipse.che.api.core.model.machine.MachineSource; import org.eclipse.che.api.local.storage.LocalStorage; import org.eclipse.che.api.local.storage.LocalStorageFactory; -import org.eclipse.che.api.machine.server.dao.SnapshotDao; +import org.eclipse.che.api.machine.server.spi.SnapshotDao; import org.eclipse.che.api.machine.server.exception.SnapshotException; import org.eclipse.che.api.machine.server.model.impl.SnapshotImpl; import org.eclipse.che.api.machine.server.model.impl.adapter.MachineSourceAdapter; @@ -29,23 +30,30 @@ import java.io.IOException; import java.util.HashMap; import java.util.List; import java.util.Map; -import java.util.Objects; import java.util.Optional; import static java.lang.String.format; import static java.util.Collections.singletonMap; +import static java.util.Objects.requireNonNull; import static java.util.stream.Collectors.toList; /** * In-memory implementation of {@link SnapshotDao}. * + *

The implementation is thread-safe guarded by this instance. + * Clients may use instance locking to perform extra, thread-safe operation. + * * @author Yevhenii Voevodin */ @Singleton public class LocalSnapshotDaoImpl implements SnapshotDao { - private final Map snapshots; - private final LocalStorage snapshotStorage; + public static final String FILENAME = "snapshots.json"; + + @VisibleForTesting + final Map snapshots; + + private final LocalStorage snapshotStorage; @Inject public LocalSnapshotDaoImpl(LocalStorageFactory storageFactory) throws IOException { @@ -56,6 +64,9 @@ public class LocalSnapshotDaoImpl implements SnapshotDao { @Override public synchronized SnapshotImpl getSnapshot(String workspaceId, String envName, String machineName) throws NotFoundException, SnapshotException { + requireNonNull(workspaceId, "Required non-null workspace id"); + requireNonNull(envName, "Required non-null environment name"); + requireNonNull(machineName, "Required non-null machine name"); final Optional snapshotOpt = doGetSnapshot(workspaceId, envName, machineName); if (!snapshotOpt.isPresent()) { throw new NotFoundException(format("Snapshot with workspace id '%s', environment name '%s', machine name %s doesn't exist", @@ -66,6 +77,7 @@ public class LocalSnapshotDaoImpl implements SnapshotDao { @Override public synchronized SnapshotImpl getSnapshot(String snapshotId) throws NotFoundException, SnapshotException { + requireNonNull(snapshotId, "Required non-null snapshot id"); final SnapshotImpl snapshot = snapshots.get(snapshotId); if (snapshot == null) { throw new NotFoundException("Snapshot with id '" + snapshotId + "' doesn't exist"); @@ -75,24 +87,35 @@ public class LocalSnapshotDaoImpl implements SnapshotDao { @Override public synchronized void saveSnapshot(SnapshotImpl snapshot) throws SnapshotException { - Objects.requireNonNull(snapshot, "Required non-null snapshot"); + requireNonNull(snapshot, "Required non-null snapshot"); + if (snapshots.containsKey(snapshot.getWorkspaceId())) { + throw new SnapshotException(format("Snapshot with id '%s' already exists", snapshot.getId())); + } final Optional opt = doGetSnapshot(snapshot.getWorkspaceId(), snapshot.getEnvName(), snapshot.getMachineName()); if (opt.isPresent()) { - snapshots.remove(opt.get().getId()); + throw new SnapshotException(format("Snapshot for machine '%s:%s:%s' already exists", + snapshot.getWorkspaceId(), + snapshot.getEnvName(), + snapshot.getMachineName())); } snapshots.put(snapshot.getId(), snapshot); } @Override - public synchronized List findSnapshots(String namespace, String workspaceId) throws SnapshotException { + public synchronized List findSnapshots(String workspaceId) throws SnapshotException { + requireNonNull(workspaceId, "Required non-null workspace id"); return snapshots.values() .stream() - .filter(snapshot -> snapshot.getNamespace().equals(namespace) && snapshot.getWorkspaceId().equals(workspaceId)) + .filter(snapshot -> snapshot.getWorkspaceId().equals(workspaceId)) .collect(toList()); } @Override public synchronized void removeSnapshot(String snapshotId) throws NotFoundException, SnapshotException { + requireNonNull(snapshotId, "Required non-null snapshot id"); + if (!snapshots.containsKey(snapshotId)) { + throw new NotFoundException(format("Snapshot with id '%s' doesn't exist", snapshotId)); + } snapshots.remove(snapshotId); } @@ -101,7 +124,6 @@ public class LocalSnapshotDaoImpl implements SnapshotDao { snapshots.putAll(snapshotStorage.loadMap(new TypeToken>() {})); } - @PreDestroy public synchronized void saveSnapshots() throws IOException { snapshotStorage.store(snapshots); } diff --git a/wsmaster/wsmaster-local/src/main/java/org/eclipse/che/api/local/LocalSshDaoImpl.java b/wsmaster/wsmaster-local/src/main/java/org/eclipse/che/api/local/LocalSshDaoImpl.java index 4d3a4f7d85..b8bfb5cfda 100644 --- a/wsmaster/wsmaster-local/src/main/java/org/eclipse/che/api/local/LocalSshDaoImpl.java +++ b/wsmaster/wsmaster-local/src/main/java/org/eclipse/che/api/local/LocalSshDaoImpl.java @@ -11,12 +11,11 @@ package org.eclipse.che.api.local; import com.google.common.annotations.VisibleForTesting; -import com.google.common.collect.ArrayListMultimap; -import com.google.common.collect.ListMultimap; import com.google.common.reflect.TypeToken; import org.eclipse.che.api.core.ConflictException; import org.eclipse.che.api.core.NotFoundException; +import org.eclipse.che.api.core.ServerException; import org.eclipse.che.api.local.storage.LocalStorage; import org.eclipse.che.api.local.storage.LocalStorageFactory; import org.eclipse.che.api.ssh.server.model.impl.SshPairImpl; @@ -28,128 +27,109 @@ import javax.inject.Inject; import javax.inject.Singleton; import java.io.IOException; import java.util.ArrayList; -import java.util.HashMap; import java.util.List; -import java.util.Map; import java.util.Optional; -import java.util.concurrent.locks.ReadWriteLock; -import java.util.concurrent.locks.ReentrantReadWriteLock; import java.util.stream.Collectors; import static java.lang.String.format; +import static java.util.Objects.requireNonNull; /** - * In-memory implementation of {@link SshDao} + * In-memory implementation of {@link SshDao}. + * + *

The implementation is thread-safe guarded by this instance. + * Clients may use instance locking to perform extra, thread-safe operation. * * @author Sergii Leschenko + * @author Yevhenii Voevodin */ @Singleton public class LocalSshDaoImpl implements SshDao { - private final ListMultimap pairs; - private final ReadWriteLock lock; - private final LocalStorage sshStorage; + + public static final String FILENAME = "ssh.json"; + + private final LocalStorage sshStorage; + + @VisibleForTesting + final List pairs; @Inject public LocalSshDaoImpl(LocalStorageFactory storageFactory) throws IOException { - pairs = ArrayListMultimap.create(); - lock = new ReentrantReadWriteLock(); - sshStorage = storageFactory.create("ssh.json"); + pairs = new ArrayList<>(); + sshStorage = storageFactory.create(FILENAME); } @Override - public void create(String owner, SshPairImpl usersSshPair) throws ConflictException { - lock.writeLock().lock(); - try { - final Optional any = find(owner, usersSshPair.getService(), usersSshPair.getName()); - if (any.isPresent()) { - throw new ConflictException(format("Ssh pair with service '%s' and name %s already exist.", - usersSshPair.getService(), - usersSshPair.getName())); - } - pairs.put(owner, usersSshPair); - } finally { - lock.writeLock().unlock(); + public synchronized void create(SshPairImpl sshPair) throws ConflictException { + requireNonNull(sshPair); + final Optional any = find(sshPair.getOwner(), sshPair.getService(), sshPair.getName()); + if (any.isPresent()) { + throw new ConflictException(format("Ssh pair with service '%s' and name %s already exist.", + sshPair.getService(), + sshPair.getName())); } + pairs.add(sshPair); } @Override - public SshPairImpl get(String owner, String service, String name) throws NotFoundException { - lock.readLock().lock(); - try { - final Optional any = find(owner, service, name); - if (any.isPresent()) { - return any.get(); - } + public synchronized SshPairImpl get(String owner, String service, String name) throws NotFoundException { + requireNonNull(owner); + requireNonNull(service); + requireNonNull(name); + final Optional any = find(owner, service, name); + if (any.isPresent()) { + return new SshPairImpl(any.get()); + } + throw new NotFoundException(format("Ssh pair with service '%s' and name '%s' was not found.", service, name)); + } + + @Override + public synchronized void remove(String owner, String service, String name) throws NotFoundException { + requireNonNull(owner); + requireNonNull(service); + requireNonNull(name); + final Optional any = find(owner, service, name); + if (!any.isPresent()) { throw new NotFoundException(format("Ssh pair with service '%s' and name '%s' was not found.", service, name)); - } finally { - lock.readLock().unlock(); } + pairs.remove(any.get()); } @Override - public void remove(String owner, String service, String name) throws NotFoundException { - lock.writeLock().lock(); - try { - final Optional any = find(owner, service, name); - if (!any.isPresent()) { - throw new NotFoundException(format("Ssh pair with service '%s' and name '%s' was not found.", service, name)); - } - pairs.remove(owner, any.get()); - } finally { - lock.writeLock().unlock(); - } + public synchronized List get(String owner) throws ServerException { + requireNonNull(owner, "Required non-null owner"); + return pairs.stream() + .filter(p -> p.getOwner().equals(owner)) + .map(SshPairImpl::new) + .collect(Collectors.toList()); } @Override - public List get(String owner, String service) { - lock.readLock().lock(); - try { - return pairs.get(owner) - .stream() - .filter(sshPair -> sshPair.getService().equals(service)) - .collect(Collectors.toList()); - } finally { - lock.readLock().unlock(); - } - } - - private Optional find(String owner, String service, String name) { - return pairs.get(owner) - .stream() - .filter(sshPair -> sshPair.getService().equals(service) - && sshPair.getName().equals(name)) - .findAny(); + public synchronized List get(String owner, String service) { + requireNonNull(owner); + requireNonNull(service); + return pairs.stream() + .filter(sshPair -> sshPair.getOwner().equals(owner) + && sshPair.getService().equals(service)) + .map(SshPairImpl::new) + .collect(Collectors.toList()); } @PostConstruct @VisibleForTesting - void loadSshPairs() { - lock.writeLock().lock(); - try { - final Map> ownerToPairs = sshStorage.loadMap(new TypeToken>>() {}); - for (Map.Entry> stringListEntry : ownerToPairs.entrySet()) { - for (SshPairImpl sshPair : stringListEntry.getValue()) { - pairs.put(stringListEntry.getKey(), sshPair); - } - } - } finally { - lock.writeLock().unlock(); - } + synchronized void loadSshPairs() { + pairs.addAll(sshStorage.loadList(new TypeToken>() {})); } - @PreDestroy - @VisibleForTesting - void saveSshPairs() throws IOException { - lock.readLock().lock(); - try { - final HashMap> ownerToPairs = new HashMap<>(); - for (Map.Entry entry : pairs.entries()) { - ownerToPairs.computeIfAbsent(entry.getKey(), s -> new ArrayList<>()); - ownerToPairs.get(entry.getKey()).add(entry.getValue()); - } - sshStorage.store(ownerToPairs); - } finally { - lock.readLock().unlock(); - } + synchronized void saveSshPairs() throws IOException { + sshStorage.store(pairs); + } + + private Optional find(String owner, String service, String name) { + return pairs.stream() + .filter(sshPair -> sshPair.getOwner().equals(owner) + && sshPair.getService().equals(service) + && sshPair.getName().equals(name)) + .findAny(); } } diff --git a/wsmaster/wsmaster-local/src/main/java/org/eclipse/che/api/local/LocalStackDaoImpl.java b/wsmaster/wsmaster-local/src/main/java/org/eclipse/che/api/local/LocalStackDaoImpl.java index c60aa82071..b4afa616ce 100644 --- a/wsmaster/wsmaster-local/src/main/java/org/eclipse/che/api/local/LocalStackDaoImpl.java +++ b/wsmaster/wsmaster-local/src/main/java/org/eclipse/che/api/local/LocalStackDaoImpl.java @@ -10,6 +10,7 @@ *******************************************************************************/ package org.eclipse.che.api.local; +import com.google.common.annotations.VisibleForTesting; import com.google.inject.Singleton; import org.eclipse.che.api.core.ConflictException; @@ -39,110 +40,91 @@ import static java.util.stream.Collectors.toList; /** * Implementation local storage for {@link Stack} * + *

The implementation is thread-safe guarded by this instance. + * Clients may use instance locking to perform extra, thread-safe operation. + * * @author Alexander Andrienko + * @author Yevhenii Voevodin */ @Singleton public class LocalStackDaoImpl implements StackDao { - private final StackLocalStorage stackStorage; - private final Map stacks; - private final ReadWriteLock lock; + @VisibleForTesting + final Map stacks; + + private final StackLocalStorage stackStorage; @Inject public LocalStackDaoImpl(StackLocalStorage stackLocalStorage) throws IOException { this.stackStorage = stackLocalStorage; this.stacks = new LinkedHashMap<>(); - this.lock = new ReentrantReadWriteLock(); } @PostConstruct - public void start() { - lock.readLock().lock(); + public synchronized void start() { stacks.putAll(stackStorage.loadMap()); - lock.readLock().unlock(); } - @PreDestroy - public void stop() throws IOException { - lock.writeLock().lock(); + public synchronized void saveStacks() throws IOException { stackStorage.store(stacks); - lock.writeLock().unlock(); } @Override - public void create(StackImpl stack) throws ConflictException, ServerException { + public synchronized void create(StackImpl stack) throws ConflictException, ServerException { requireNonNull(stack, "Stack required"); - lock.writeLock().lock(); - try { - if (stacks.containsKey(stack.getId())) { - throw new ConflictException(format("Stack with id %s is already exist", stack.getId())); - } - stacks.put(stack.getId(), stack); - } finally { - lock.writeLock().unlock(); + if (stacks.containsKey(stack.getId())) { + throw new ConflictException(format("Stack with id %s is already exist", stack.getId())); } + if (stacks.values() + .stream() + .anyMatch(s -> s.getName().equals(stack.getName()))) { + throw new ConflictException(format("Stack with name '%s' already exists", stack.getName())); + } + stacks.put(stack.getId(), new StackImpl(stack)); } @Override - public StackImpl getById(String id) throws NotFoundException { + public synchronized StackImpl getById(String id) throws NotFoundException { requireNonNull(id, "Stack id required"); - lock.readLock().lock(); - try { - final StackImpl stack = stacks.get(id); - if (stack == null) { - throw new NotFoundException(format("Stack with id %s was not found", id)); - } - return new StackImpl(stack); - } finally { - lock.readLock().unlock(); + final StackImpl stack = stacks.get(id); + if (stack == null) { + throw new NotFoundException(format("Stack with id %s was not found", id)); } + return new StackImpl(stack); } @Override - public void remove(String id) throws ServerException { + public synchronized void remove(String id) throws ServerException { requireNonNull(id, "Stack id required"); - lock.writeLock().lock(); - try { - stacks.remove(id); - } finally { - lock.writeLock().unlock(); - } + stacks.remove(id); } @Override - public StackImpl update(StackImpl update) throws NotFoundException, ServerException { + public synchronized StackImpl update(StackImpl update) throws NotFoundException, ServerException, ConflictException { requireNonNull(update, "Stack required"); requireNonNull(update.getId(), "Stack id required"); - lock.writeLock().lock(); - try { - String updateId = update.getId(); - if (!stacks.containsKey(updateId)) { - throw new NotFoundException(format("Stack with id %s was not found", updateId)); - } - stacks.replace(updateId, update); - return new StackImpl(update); - } finally { - lock.writeLock().unlock(); + String updateId = update.getId(); + if (!stacks.containsKey(updateId)) { + throw new NotFoundException(format("Stack with id %s was not found", updateId)); } + if (stacks.values() + .stream() + .anyMatch(stack -> stack.getName().equals(update.getName()) && !stack.getId().equals(updateId))) { + throw new ConflictException(format("Stack with name '%s' already exists", updateId)); + } + stacks.replace(updateId, new StackImpl(update)); + return new StackImpl(update); } @Override - public List searchStacks(String user, @Nullable List tags, int skipCount, int maxItems) { - lock.readLock().lock(); - try { - Stream stacksStream = stacks.values() - .stream() - .skip(skipCount) - .filter(decoratedStack -> tags == null || - decoratedStack.getTags().containsAll(tags)); - if (maxItems != 0) { - stacksStream = stacksStream.limit(maxItems); - } - - return stacksStream.map(StackImpl::new) - .collect(toList()); - } finally { - lock.readLock().unlock(); + public synchronized List searchStacks(String user, @Nullable List tags, int skipCount, int maxItems) { + Stream stream = stacks.values() + .stream() + .skip(skipCount) + .filter(s -> tags == null || s.getTags().containsAll(tags)); + if (maxItems != 0) { + stream = stream.limit(maxItems); } + return stream.map(StackImpl::new).collect(toList()); } } diff --git a/wsmaster/wsmaster-local/src/main/java/org/eclipse/che/api/local/LocalUserDaoImpl.java b/wsmaster/wsmaster-local/src/main/java/org/eclipse/che/api/local/LocalUserDaoImpl.java index 8afeac36f6..6f4cd6ae22 100644 --- a/wsmaster/wsmaster-local/src/main/java/org/eclipse/che/api/local/LocalUserDaoImpl.java +++ b/wsmaster/wsmaster-local/src/main/java/org/eclipse/che/api/local/LocalUserDaoImpl.java @@ -16,8 +16,8 @@ import com.google.common.reflect.TypeToken; import org.eclipse.che.api.core.ConflictException; import org.eclipse.che.api.core.NotFoundException; +import org.eclipse.che.api.core.Page; import org.eclipse.che.api.core.ServerException; -import org.eclipse.che.api.core.UnauthorizedException; import org.eclipse.che.api.core.model.user.User; import org.eclipse.che.api.local.storage.LocalStorage; import org.eclipse.che.api.local.storage.LocalStorageFactory; @@ -27,179 +27,144 @@ import org.eclipse.che.api.user.server.spi.UserDao; import javax.annotation.PostConstruct; import javax.annotation.PreDestroy; import javax.inject.Inject; -import javax.inject.Named; import javax.inject.Singleton; import java.io.IOException; -import java.util.Collection; import java.util.Collections; import java.util.HashMap; import java.util.HashSet; +import java.util.LinkedHashSet; import java.util.Map; import java.util.Optional; -import java.util.Set; -import java.util.concurrent.locks.ReadWriteLock; -import java.util.concurrent.locks.ReentrantReadWriteLock; import java.util.function.Predicate; +import java.util.stream.Collectors; import static java.lang.String.format; import static java.util.Objects.requireNonNull; +import static java.util.stream.Collectors.toList; /** + * In-memory implementation of {@link UserDao}. + * + *

The implementation is thread-safe guarded by this instance. + * Clients may use instance locking to perform extra, thread-safe operation. + * * @author Anton Korneta * @author Yevhenii Voevodin */ @Singleton public class LocalUserDaoImpl implements UserDao { - @VisibleForTesting + public static final String FILENAME = "users.json"; + final Map users; - private final ReadWriteLock rwLock; - private final LocalStorage userStorage; + private final LocalStorage userStorage; @Inject public LocalUserDaoImpl(LocalStorageFactory storageFactory) throws IOException { this.users = new HashMap<>(); - rwLock = new ReentrantReadWriteLock(); - userStorage = storageFactory.create("users.json"); + userStorage = storageFactory.create(FILENAME); } @Inject @PostConstruct - public void start(@Named("codenvy.local.infrastructure.users") Set defaultUsers) { - final Map storedUsers = userStorage.loadMap(new TypeToken>() {}); - rwLock.writeLock().lock(); - try { - final Collection preloadedUsers = storedUsers.isEmpty() ? defaultUsers : storedUsers.values(); - for (UserImpl defaultUser : preloadedUsers) { - users.put(defaultUser.getId(), new UserImpl(defaultUser)); - } - } finally { - rwLock.writeLock().unlock(); - } + public synchronized void start() { + users.putAll(userStorage.loadMap(new TypeToken>() {})); } - @PreDestroy - public void stop() throws IOException { - rwLock.readLock().lock(); - try { - userStorage.store(new HashMap<>(users)); - } finally { - rwLock.readLock().unlock(); - } + public synchronized void saveUsers() throws IOException { + userStorage.store(new HashMap<>(users)); } @Override - public String authenticate(String aliasOrNameOrEmail, String password) throws UnauthorizedException, ServerException { - requireNonNull(aliasOrNameOrEmail); + public synchronized UserImpl getByAliasAndPassword(String emailOrName, String password) throws ServerException, + NotFoundException { + requireNonNull(emailOrName); requireNonNull(password); - rwLock.readLock().lock(); - try { - final Optional userOpt = users.values() - .stream() - .filter(user -> user.getName().equals(aliasOrNameOrEmail) - || user.getEmail().equals(aliasOrNameOrEmail) - || user.getAliases().contains(aliasOrNameOrEmail)) - .findAny(); - if (!userOpt.isPresent() || !userOpt.get().getPassword().equals(password)) { - throw new UnauthorizedException(format("Authentication failed for user '%s'", aliasOrNameOrEmail)); - } - return userOpt.get().getId(); - } finally { - rwLock.readLock().unlock(); + final Optional userOpt = users.values() + .stream() + .filter(user -> user.getName().equals(emailOrName) + || user.getEmail().equals(emailOrName)) + .findAny(); + if (!userOpt.isPresent() || !userOpt.get().getPassword().equals(password)) { + throw new NotFoundException(format("User '%s' doesn't exist", emailOrName)); } + return erasePassword(userOpt.get()); } @Override - public void create(UserImpl newUser) throws ConflictException { + public synchronized void create(UserImpl newUser) throws ConflictException { requireNonNull(newUser); - rwLock.writeLock().lock(); - try { - if (users.containsKey(newUser.getId())) { - throw new ConflictException(format("Couldn't create user, user with id '%s' already exists", - newUser.getId())); - } - checkConflicts(newUser, "create"); - users.put(newUser.getId(), new UserImpl(newUser)); - } finally { - rwLock.writeLock().unlock(); + if (users.containsKey(newUser.getId())) { + throw new ConflictException(format("Couldn't create user, user with id '%s' already exists", + newUser.getId())); } + checkConflicts(newUser, "create"); + users.put(newUser.getId(), new UserImpl(newUser)); } @Override - public void update(UserImpl update) throws NotFoundException, ConflictException { + public synchronized void update(UserImpl update) throws NotFoundException, ConflictException { requireNonNull(update); - rwLock.writeLock().lock(); - try { - final UserImpl user = users.get(update.getId()); - if (user == null) { - throw new NotFoundException(format("User with id '%s' doesn't exist", update.getId())); - } - checkConflicts(update, "update"); - users.put(update.getId(), new UserImpl(update)); - } finally { - rwLock.writeLock().unlock(); + final UserImpl user = users.get(update.getId()); + if (user == null) { + throw new NotFoundException(format("User with id '%s' doesn't exist", update.getId())); } + checkConflicts(update, "update"); + users.put(update.getId(), new UserImpl(update)); } @Override - public void remove(String id) { + public synchronized void remove(String id) { requireNonNull(id); - rwLock.writeLock().lock(); - try { - users.remove(id); - } finally { - rwLock.writeLock().unlock(); - } + users.remove(id); } @Override - public UserImpl getByAlias(String alias) throws NotFoundException { + public synchronized UserImpl getByAlias(String alias) throws NotFoundException { requireNonNull(alias, "Required non-null alias"); - rwLock.readLock().lock(); - try { - return new UserImpl(find(user -> user.getAliases().contains(alias), "alias", alias)); - } finally { - rwLock.readLock().unlock(); - } + return erasePassword(find(user -> user.getAliases().contains(alias), "alias", alias)); } @Override - public UserImpl getById(String id) throws NotFoundException { + public synchronized UserImpl getById(String id) throws NotFoundException { requireNonNull(id, "Required non-null id"); - rwLock.readLock().lock(); - try { - final User user = users.get(id); - if (user == null) { - throw new NotFoundException(format("User with id '%s' doesn't exist", id)); - } - return new UserImpl(user); - } finally { - rwLock.readLock().unlock(); + final User user = users.get(id); + if (user == null) { + throw new NotFoundException(format("User with id '%s' doesn't exist", id)); } + return erasePassword(user); } @Override - public UserImpl getByName(String name) throws NotFoundException { + public synchronized UserImpl getByName(String name) throws NotFoundException { requireNonNull(name, "Required non-null name"); - rwLock.readLock().lock(); - try { - return new UserImpl(find(user -> user.getName().equals(name), "name", name)); - } finally { - rwLock.readLock().unlock(); - } + return erasePassword(find(user -> user.getName().equals(name), "name", name)); } @Override - public UserImpl getByEmail(String email) throws NotFoundException, ServerException { + public synchronized UserImpl getByEmail(String email) throws NotFoundException, ServerException { requireNonNull(email, "Required non-null email"); - rwLock.readLock().lock(); - try { - return new UserImpl(find(user -> user.getEmail().equals(email), "email", email)); - } finally { - rwLock.readLock().unlock(); - } + return erasePassword(find(user -> user.getEmail().equals(email), "email", email)); + } + + @Override + public Page getAll(int maxItems, int skipCount) throws ServerException { + return new Page<>(users.values() + .stream() + .skip(skipCount) + .limit(maxItems) + .map(LocalUserDaoImpl::erasePassword) + .collect(Collectors.toCollection(LinkedHashSet::new)), + skipCount, + maxItems, + users.size()); + } + + @Override + public long getTotalCount() throws ServerException { + return users.size(); } private void checkConflicts(UserImpl user, String operation) throws ConflictException { @@ -238,4 +203,13 @@ public class LocalUserDaoImpl implements UserDao { } return userOpt.get(); } + + // Returns user instance copy without password + private static UserImpl erasePassword(User source) { + return new UserImpl(source.getId(), + source.getEmail(), + source.getName(), + null, + source.getAliases()); + } } diff --git a/wsmaster/wsmaster-local/src/main/java/org/eclipse/che/api/local/LocalWorkspaceDaoImpl.java b/wsmaster/wsmaster-local/src/main/java/org/eclipse/che/api/local/LocalWorkspaceDaoImpl.java index 064fd8b9c0..00eaee5498 100644 --- a/wsmaster/wsmaster-local/src/main/java/org/eclipse/che/api/local/LocalWorkspaceDaoImpl.java +++ b/wsmaster/wsmaster-local/src/main/java/org/eclipse/che/api/local/LocalWorkspaceDaoImpl.java @@ -10,6 +10,7 @@ *******************************************************************************/ package org.eclipse.che.api.local; +import com.google.common.annotations.VisibleForTesting; import com.google.common.collect.ImmutableMap; import com.google.common.reflect.TypeToken; @@ -18,8 +19,6 @@ import org.eclipse.che.api.core.NotFoundException; import org.eclipse.che.api.core.ServerException; import org.eclipse.che.api.core.model.machine.Recipe; import org.eclipse.che.api.core.model.project.ProjectConfig; -import org.eclipse.che.api.core.model.workspace.Workspace; -import org.eclipse.che.api.core.model.workspace.WorkspaceConfig; import org.eclipse.che.api.core.model.workspace.WorkspaceStatus; import org.eclipse.che.api.local.storage.LocalStorage; import org.eclipse.che.api.local.storage.LocalStorageFactory; @@ -30,7 +29,6 @@ import org.eclipse.che.api.workspace.server.model.impl.WorkspaceImpl; import org.eclipse.che.api.workspace.server.spi.WorkspaceDao; import javax.annotation.PostConstruct; -import javax.annotation.PreDestroy; import javax.inject.Inject; import javax.inject.Singleton; import java.io.IOException; @@ -41,6 +39,7 @@ import java.util.Map; import java.util.Optional; import static java.lang.String.format; +import static java.util.Objects.requireNonNull; import static java.util.stream.Collectors.toList; /** @@ -56,7 +55,10 @@ import static java.util.stream.Collectors.toList; @Singleton public class LocalWorkspaceDaoImpl implements WorkspaceDao { - private final Map workspaces; + public static final String FILENAME = "workspaces.json"; + + @VisibleForTesting + final Map workspaces; private final LocalStorage localStorage; @Inject @@ -65,7 +67,7 @@ public class LocalWorkspaceDaoImpl implements WorkspaceDao { ImmutableMap.of(Recipe.class, new RecipeTypeAdapter(), ProjectConfig.class, new ProjectConfigAdapter(), WorkspaceConfigImpl.class, new WorkspaceConfigDeserializer(cfgAdapter)); - this.localStorage = factory.create("workspaces.json", adapters); + this.localStorage = factory.create(FILENAME, adapters); this.workspaces = new HashMap<>(); } @@ -77,68 +79,82 @@ public class LocalWorkspaceDaoImpl implements WorkspaceDao { } } - @PreDestroy public synchronized void saveWorkspaces() throws IOException { localStorage.store(workspaces); } @Override public synchronized WorkspaceImpl create(WorkspaceImpl workspace) throws ConflictException, ServerException { + requireNonNull(workspace, "Required non-null workspace"); if (workspaces.containsKey(workspace.getId())) { throw new ConflictException("Workspace with id " + workspace.getId() + " already exists"); } - if (find(workspace.getConfig().getName(), workspace.getNamespace()).isPresent()) { + if (find(workspace.getName(), workspace.getNamespace()).isPresent()) { throw new ConflictException(format("Workspace with name %s and owner %s already exists", workspace.getConfig().getName(), workspace.getNamespace())); } + workspace.setRuntime(null); workspace.setStatus(WorkspaceStatus.STOPPED); - workspaces.put(workspace.getId(), new WorkspaceImpl(workspace)); + workspaces.put(workspace.getId(), new WorkspaceImpl(workspace, workspace.getAccount())); return workspace; } @Override - public synchronized WorkspaceImpl update(WorkspaceImpl workspace) - throws NotFoundException, ConflictException, ServerException { + public synchronized WorkspaceImpl update(WorkspaceImpl workspace) throws NotFoundException, + ConflictException, + ServerException { + requireNonNull(workspace, "Required non-null workspace"); if (!workspaces.containsKey(workspace.getId())) { throw new NotFoundException("Workspace with id " + workspace.getId() + " was not found"); } + if (find(workspace.getName(), workspace.getNamespace()).isPresent()) { + throw new ConflictException(format("Workspace with name %s and owner %s already exists", + workspace.getConfig().getName(), + workspace.getNamespace())); + } workspace.setStatus(null); workspace.setRuntime(null); - workspaces.put(workspace.getId(), new WorkspaceImpl(workspace)); + workspaces.put(workspace.getId(), new WorkspaceImpl(workspace, workspace.getAccount())); return workspace; } @Override public synchronized void remove(String id) throws ConflictException, ServerException { + requireNonNull(id, "Required non-null id"); workspaces.remove(id); } @Override public synchronized WorkspaceImpl get(String id) throws NotFoundException, ServerException { + requireNonNull(id, "Required non-null id"); final WorkspaceImpl workspace = workspaces.get(id); if (workspace == null) { throw new NotFoundException("Workspace with id " + id + " was not found"); } - return new WorkspaceImpl(workspace); + return new WorkspaceImpl(workspace, workspace.getAccount()); } @Override public synchronized WorkspaceImpl get(String name, String namespace) throws NotFoundException, ServerException { + requireNonNull(name, "Required non-null name"); + requireNonNull(namespace, "Required non-null namespace"); final Optional wsOpt = find(name, namespace); if (!wsOpt.isPresent()) { throw new NotFoundException(format("Workspace with name %s and owner %s was not found", name, namespace)); } - return new WorkspaceImpl(wsOpt.get()); + WorkspaceImpl workspace = wsOpt.get(); + return new WorkspaceImpl(workspace, workspace.getAccount()); } @Override public synchronized List getByNamespace(String namespace) throws ServerException { + requireNonNull(namespace, "Required non-null namespace"); return workspaces.values() .stream() .filter(ws -> ws.getNamespace().equals(namespace)) - .map(WorkspaceImpl::new) + .map(ws -> new WorkspaceImpl(ws, ws.getAccount())) .collect(toList()); } diff --git a/wsmaster/wsmaster-local/src/main/java/org/eclipse/che/api/local/WorkspaceDeserializer.java b/wsmaster/wsmaster-local/src/main/java/org/eclipse/che/api/local/WorkspaceDeserializer.java new file mode 100644 index 0000000000..e94b02cafe --- /dev/null +++ b/wsmaster/wsmaster-local/src/main/java/org/eclipse/che/api/local/WorkspaceDeserializer.java @@ -0,0 +1,36 @@ +/******************************************************************************* + * Copyright (c) 2012-2016 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 + *******************************************************************************/ +package org.eclipse.che.api.local; + +import com.google.gson.Gson; +import com.google.gson.JsonDeserializationContext; +import com.google.gson.JsonDeserializer; +import com.google.gson.JsonElement; +import com.google.gson.JsonParseException; + +import org.eclipse.che.account.shared.model.Account; +import org.eclipse.che.account.spi.AccountImpl; +import org.eclipse.che.api.workspace.server.model.impl.WorkspaceImpl; + +import java.lang.reflect.Type; + +/** + * @author Mihail Kuznyetsov + */ +public class WorkspaceDeserializer implements JsonDeserializer { + @Override + public WorkspaceImpl deserialize(JsonElement jsonElement, Type type, JsonDeserializationContext jsonDeserializationContext) + throws JsonParseException { + WorkspaceImpl impl = new Gson().fromJson(jsonElement, WorkspaceImpl.class); + impl.setAccount(new AccountImpl(null, jsonElement.getAsJsonObject().get("namespace").getAsString(), null)); + return impl; + } +} diff --git a/wsmaster/wsmaster-local/src/main/java/org/eclipse/che/api/local/storage/LocalStorage.java b/wsmaster/wsmaster-local/src/main/java/org/eclipse/che/api/local/storage/LocalStorage.java index 8967042f28..ebe9cde677 100644 --- a/wsmaster/wsmaster-local/src/main/java/org/eclipse/che/api/local/storage/LocalStorage.java +++ b/wsmaster/wsmaster-local/src/main/java/org/eclipse/che/api/local/storage/LocalStorage.java @@ -25,6 +25,7 @@ import java.io.IOException; import java.io.Reader; import java.io.Writer; import java.nio.charset.Charset; +import java.nio.file.Path; import java.util.Collections; import java.util.List; import java.util.Map; @@ -117,4 +118,9 @@ public class LocalStorage { } return result; } + + /** Returns the file managed by this storage. */ + public File getFile() { + return storedFile; + } } \ No newline at end of file diff --git a/wsmaster/wsmaster-local/src/main/java/org/eclipse/che/api/local/storage/stack/StackLocalStorage.java b/wsmaster/wsmaster-local/src/main/java/org/eclipse/che/api/local/storage/stack/StackLocalStorage.java index 0fefb03cad..731c29f6cc 100644 --- a/wsmaster/wsmaster-local/src/main/java/org/eclipse/che/api/local/storage/stack/StackLocalStorage.java +++ b/wsmaster/wsmaster-local/src/main/java/org/eclipse/che/api/local/storage/stack/StackLocalStorage.java @@ -48,7 +48,7 @@ public class StackLocalStorage { private static final Logger LOG = LoggerFactory.getLogger(StackLocalStorage.class); - private static final String STACK_STORAGE_FILE = "stacks.json"; + public static final String STACK_STORAGE_FILE = "stacks.json"; private static final String ICON_FOLDER_NAME = "images"; private final LocalStorage localStorage; diff --git a/wsmaster/wsmaster-local/src/test/java/org/eclipse/che/api/local/LocalDataMigratorTest.java b/wsmaster/wsmaster-local/src/test/java/org/eclipse/che/api/local/LocalDataMigratorTest.java new file mode 100644 index 0000000000..05f1f6b104 --- /dev/null +++ b/wsmaster/wsmaster-local/src/test/java/org/eclipse/che/api/local/LocalDataMigratorTest.java @@ -0,0 +1,278 @@ +/******************************************************************************* + * Copyright (c) 2012-2016 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 + *******************************************************************************/ +package org.eclipse.che.api.local; + +import com.google.gson.Gson; +import com.google.gson.JsonElement; +import com.google.gson.JsonObject; +import com.google.gson.JsonPrimitive; +import com.google.gson.JsonSerializationContext; +import com.google.gson.JsonSerializer; + +import org.eclipse.che.api.core.NotFoundException; +import org.eclipse.che.api.core.ServerException; +import org.eclipse.che.api.local.storage.LocalStorageFactory; +import org.eclipse.che.api.local.storage.stack.StackLocalStorage; +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.model.impl.SshPairImpl; +import org.eclipse.che.api.ssh.server.spi.SshDao; +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.WorkspaceConfigJsonAdapter; +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.api.workspace.server.stack.StackJsonAdapter; +import org.eclipse.che.commons.lang.IoUtil; +import org.mockito.Mock; +import org.mockito.testng.MockitoTestNGListener; +import org.testng.annotations.AfterMethod; +import org.testng.annotations.BeforeMethod; +import org.testng.annotations.DataProvider; +import org.testng.annotations.Listeners; +import org.testng.annotations.Test; + +import java.lang.reflect.Type; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.Map; + +import static java.util.Collections.emptyMap; +import static java.util.Collections.singletonList; +import static java.util.Collections.singletonMap; +import static org.mockito.Matchers.any; +import static org.mockito.Matchers.anyString; +import static org.mockito.Mockito.doReturn; +import static org.mockito.Mockito.doThrow; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; +import static org.testng.Assert.assertFalse; +import static org.testng.Assert.assertTrue; + +/** + * Tests {@link LocalDataMigrator}. + * + * @author Yevhenii Voevodin + */ +@Listeners(MockitoTestNGListener.class) +public class LocalDataMigratorTest { + + private Path baseDir; + private LocalStorageFactory factory; + + @Mock + private UserDao userDao; + + @Mock + private ProfileDao profileDao; + + @Mock + private PreferenceDao preferenceDao; + + @Mock + private SshDao sshDao; + + @Mock + private WorkspaceDao workspaceDao; + + @Mock + private SnapshotDao snapshotDao; + + @Mock + private RecipeDao recipeDao; + + @Mock + private StackDao stackDao; + + @Mock + private StackJsonAdapter stackJsonAdapter; + + @Mock + private WorkspaceConfigJsonAdapter workspaceCfgJsonAdapter; + + private LocalDataMigrator dataMigrator; + + @BeforeMethod + private void setUp() throws Exception { + baseDir = Files.createTempDirectory(Paths.get("/tmp"), "test"); + factory = new LocalStorageFactory(baseDir.toString()); + dataMigrator = new LocalDataMigrator(); + storeTestData(); + doThrow(new NotFoundException("not-found")).when(userDao).getById(anyString()); + // needed by workspace + when(userDao.getByName(anyString())).thenReturn(new UserImpl("id", "email", "name")); + doThrow(new NotFoundException("not-found")).when(profileDao).getById(anyString()); + doReturn(emptyMap()).when(preferenceDao).getPreferences(anyString()); + doThrow(new NotFoundException("not-found")).when(sshDao).get(anyString(), anyString(), anyString()); + doThrow(new NotFoundException("not-found")).when(workspaceDao).get(anyString()); + doThrow(new NotFoundException("not-found")).when(snapshotDao).getSnapshot(anyString()); + doThrow(new NotFoundException("not-found")).when(recipeDao).getById(anyString()); + doThrow(new NotFoundException("not-found")).when(stackDao).getById(anyString()); + } + + @AfterMethod + private void cleanUp() { + IoUtil.deleteRecursive(baseDir.toFile()); + } + + @Test(dataProvider = "successfulMigrationAttempts") + public void shouldMigrateLocalData(String fileName, TestAction verification) throws Exception { + dataMigrator.performMigration(baseDir.toString(), + userDao, + profileDao, + preferenceDao, + sshDao, + workspaceDao, + snapshotDao, + recipeDao, + stackDao, + stackJsonAdapter, + workspaceCfgJsonAdapter); + verification.perform(); + + assertFalse(Files.exists(baseDir.resolve(fileName))); + assertTrue(Files.exists(baseDir.resolve(fileName + ".backup"))); + } + + @Test(expectedExceptions = Exception.class, dataProvider = "failOnMigrateAttempts") + public void shouldFailIfMigrationOfAnyOfEntitiesFailed(TestAction failOnMigrate) throws Exception { + failOnMigrate.perform(); + + dataMigrator.performMigration(baseDir.toString(), + userDao, + profileDao, + preferenceDao, + sshDao, + workspaceDao, + snapshotDao, + recipeDao, + stackDao, + stackJsonAdapter, + workspaceCfgJsonAdapter); + } + + @DataProvider(name = "successfulMigrationAttempts") + public Object[][] successfulMigrationAttempts() { + return new Object[][] { + { + LocalUserDaoImpl.FILENAME, + (TestAction)() -> verify(userDao).create(any()) + }, + { + LocalProfileDaoImpl.FILENAME, + (TestAction)() -> verify(profileDao).create(any()) + }, + { + LocalPreferenceDaoImpl.FILENAME, + (TestAction)() -> verify(preferenceDao).setPreferences(anyString(), any()) + }, + { + LocalSshDaoImpl.FILENAME, + (TestAction)() -> verify(sshDao).create(any()) + }, + { + LocalWorkspaceDaoImpl.FILENAME, + (TestAction)() -> verify(workspaceDao).create(any()) + }, + { + LocalSnapshotDaoImpl.FILENAME, + (TestAction)() -> verify(snapshotDao).saveSnapshot(any()) + }, + { + LocalRecipeDaoImpl.FILENAME, + (TestAction)() -> verify(recipeDao).create(any()) + }, + { + StackLocalStorage.STACK_STORAGE_FILE, + (TestAction)() -> verify(stackDao).create(any()) + } + }; + } + + @DataProvider(name = "failOnMigrateAttempts") + public Object[][] failOnMigrateAttempts() { + return new TestAction[][] { + {() -> doThrow(new ServerException("fail")).when(userDao).create(any())}, + {() -> doThrow(new ServerException("fail")).when(profileDao).create(any())}, + {() -> doThrow(new ServerException("fail")).when(preferenceDao).setPreferences(anyString(), any())}, + {() -> doThrow(new ServerException("fail")).when(sshDao).create(any())}, + {() -> doThrow(new ServerException("fail")).when(workspaceDao).create(any())}, + {() -> doThrow(new ServerException("fail")).when(snapshotDao).saveSnapshot(any())}, + {() -> doThrow(new ServerException("fail")).when(recipeDao).create(any())}, + {() -> doThrow(new ServerException("fail")).when(stackDao).create(any())} + }; + } + + private void storeTestData() throws Exception { + final UserImpl user = new UserImpl("id", "email", "name"); + final ProfileImpl profile = new ProfileImpl(user.getId()); + final Map prefs = singletonMap("key", "value"); + final SshPairImpl sshPair = new SshPairImpl(user.getId(), "service", "name", "public", "private"); + final WorkspaceImpl workspace = new WorkspaceImpl("id", user.getAccount(), new WorkspaceConfigImpl()); + final SnapshotImpl snapshot = new SnapshotImpl(); + snapshot.setId("snapshotId"); + snapshot.setWorkspaceId(workspace.getId()); + final RecipeImpl recipe = new RecipeImpl(); + recipe.setId("id"); + recipe.setCreator(user.getId()); + final StackImpl stack = new StackImpl(); + stack.setId("id"); + stack.setName("name"); + factory.create(LocalUserDaoImpl.FILENAME).store(singletonMap(user.getId(), user)); + factory.create(LocalProfileDaoImpl.FILENAME).store(singletonMap(profile.getUserId(), profile)); + factory.create(LocalPreferenceDaoImpl.FILENAME).store(singletonMap(user.getId(), prefs)); + factory.create(LocalSshDaoImpl.FILENAME, singletonMap(SshPairImpl.class, new SshSerializer())) + .store(singletonMap(sshPair.getOwner(), singletonList(sshPair))); + factory.create(LocalWorkspaceDaoImpl.FILENAME, singletonMap(WorkspaceImpl.class, new WorkspaceSerializer())) + .store(singletonMap(workspace.getId(), workspace)); + factory.create(LocalSnapshotDaoImpl.FILENAME).store(singletonMap(snapshot.getId(), snapshot)); + factory.create(LocalRecipeDaoImpl.FILENAME).store(singletonMap(recipe.getId(), recipe)); + factory.create(StackLocalStorage.STACK_STORAGE_FILE).store(singletonMap(stack.getId(), stack)); + } + + @FunctionalInterface + private interface TestAction { + void perform() throws Exception; + } + + public static class WorkspaceSerializer implements JsonSerializer { + + @Override + public JsonElement serialize(WorkspaceImpl src, Type typeOfSrc, JsonSerializationContext context) { + JsonElement result = new Gson().toJsonTree(src, WorkspaceImpl.class); + result.getAsJsonObject().addProperty("namespace", src.getNamespace()); + result.getAsJsonObject().remove("account"); + return result; + } + } + + public static class SshSerializer implements JsonSerializer { + + @Override + public JsonElement serialize(SshPairImpl sshPair, Type type, JsonSerializationContext jsonSerializationContext) { + JsonObject result = new JsonObject(); + result.add("service", new JsonPrimitive(sshPair.getService())); + result.add("name", new JsonPrimitive(sshPair.getName())); + result.add("privateKey", new JsonPrimitive(sshPair.getPublicKey())); + result.add("publicKey", new JsonPrimitive(sshPair.getPrivateKey())); + return result; + } + } +} diff --git a/wsmaster/wsmaster-local/src/test/java/org/eclipse/che/api/local/LocalMapTckRepository.java b/wsmaster/wsmaster-local/src/test/java/org/eclipse/che/api/local/LocalMapTckRepository.java new file mode 100644 index 0000000000..d651048516 --- /dev/null +++ b/wsmaster/wsmaster-local/src/test/java/org/eclipse/che/api/local/LocalMapTckRepository.java @@ -0,0 +1,30 @@ +/******************************************************************************* + * Copyright (c) 2012-2016 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 + *******************************************************************************/ +package org.eclipse.che.api.local; + +import org.eclipse.che.commons.annotation.Nullable; + +import java.util.Map; +import java.util.function.Function; + +/** + * Simplifies implementation of TckRepository for local data access objects which use map for backend. + * + * @param + * the type of the repository + * @author Yevhenii Voevodin + */ +public class LocalMapTckRepository extends LocalTckRepository, T> { + + public LocalMapTckRepository(Map storage, Function keyMapper, @Nullable Object mutex) { + super(storage, (s, entity) -> storage.put(keyMapper.apply(entity), entity), Map::clear, mutex); + } +} diff --git a/wsmaster/wsmaster-local/src/test/java/org/eclipse/che/api/local/LocalRecipeDaoImplTest.java b/wsmaster/wsmaster-local/src/test/java/org/eclipse/che/api/local/LocalRecipeDaoImplTest.java index e59c26f68e..107718e6cf 100644 --- a/wsmaster/wsmaster-local/src/test/java/org/eclipse/che/api/local/LocalRecipeDaoImplTest.java +++ b/wsmaster/wsmaster-local/src/test/java/org/eclipse/che/api/local/LocalRecipeDaoImplTest.java @@ -14,7 +14,6 @@ import com.google.gson.Gson; import com.google.gson.GsonBuilder; import org.eclipse.che.api.core.NotFoundException; -import org.eclipse.che.api.core.acl.AclEntryImpl; import org.eclipse.che.api.local.storage.LocalStorageFactory; import org.eclipse.che.api.machine.server.recipe.RecipeImpl; import org.mockito.testng.MockitoTestNGListener; @@ -26,11 +25,11 @@ import java.net.URL; import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; -import java.util.Collections; import java.util.List; import static java.nio.file.Files.readAllBytes; import static java.util.Arrays.asList; +import static java.util.Collections.emptyList; import static java.util.Collections.singletonList; import static java.util.Collections.singletonMap; import static org.testng.Assert.assertEquals; @@ -112,25 +111,13 @@ public class LocalRecipeDaoImplTest { recipeDao.getById(recipe.getId()); } - @Test - public void shouldBeAbleToUpdateRecipeWithoutAcl() throws Exception { - recipeDao.create(createRecipe().withAcl(null)); - final RecipeImpl newRecipe = createRecipe().withDescription("new description") - .withScript("FROM che/ubuntu_jdk") - .withAcl(null); - - final RecipeImpl stored = recipeDao.update(newRecipe); - - assertEquals(newRecipe, stored); - } - @Test public void shouldBeAbleToSearchRecipeByTag() throws Exception { final RecipeImpl toFind = createRecipe(); recipeDao.create(toFind); recipeDao.create(createRecipe().withId("recipe321") .withType("custom") - .withTags(Collections.emptyList())); + .withTags(emptyList())); final List search = recipeDao.search("creator", singletonList("java"), "dockerfile", 0, 0); @@ -145,7 +132,6 @@ public class LocalRecipeDaoImplTest { "dockerfile", "FROM che/ubuntu", asList("java", "ubuntu"), - "Che ubuntu", - singletonList(new AclEntryImpl("creator", asList("read", "update")))); + "Che ubuntu"); } } diff --git a/wsmaster/wsmaster-local/src/test/java/org/eclipse/che/api/local/LocalSnapshotDaoTest.java b/wsmaster/wsmaster-local/src/test/java/org/eclipse/che/api/local/LocalSnapshotDaoTest.java index 3c04c4c99d..7a29ebd304 100644 --- a/wsmaster/wsmaster-local/src/test/java/org/eclipse/che/api/local/LocalSnapshotDaoTest.java +++ b/wsmaster/wsmaster-local/src/test/java/org/eclipse/che/api/local/LocalSnapshotDaoTest.java @@ -84,7 +84,6 @@ public class LocalSnapshotDaoTest { .generateId() .setType("docker") .setMachineSource(machineSource) - .setNamespace("user123") .setWorkspaceId("workspace123") .setMachineName("machine123") .setEnvName("env123") diff --git a/wsmaster/wsmaster-local/src/test/java/org/eclipse/che/api/local/LocalSshDaoImplTest.java b/wsmaster/wsmaster-local/src/test/java/org/eclipse/che/api/local/LocalSshDaoImplTest.java index 185d4b5342..445f8442bc 100644 --- a/wsmaster/wsmaster-local/src/test/java/org/eclipse/che/api/local/LocalSshDaoImplTest.java +++ b/wsmaster/wsmaster-local/src/test/java/org/eclipse/che/api/local/LocalSshDaoImplTest.java @@ -56,16 +56,16 @@ public class LocalSshDaoImplTest { public void testSshPairsSerialization() throws Exception { SshPairImpl pair = createPair(); - sshDao.create("owner", pair); + sshDao.create(pair); sshDao.saveSshPairs(); - assertEquals(GSON.toJson(ImmutableMap.of("owner", singletonList(pair))), new String(readAllBytes(sshPath))); + assertEquals(GSON.toJson(singletonList(pair)), new String(readAllBytes(sshPath))); } @Test public void testSshPairsDeserialization() throws Exception { SshPairImpl pair = createPair(); - Files.write(sshPath, GSON.toJson(ImmutableMap.of("owner", singletonList(pair))).getBytes()); + Files.write(sshPath, GSON.toJson(singletonList(pair)).getBytes()); sshDao.loadSshPairs(); @@ -75,6 +75,6 @@ public class LocalSshDaoImplTest { } private static SshPairImpl createPair() { - return new SshPairImpl("service", "name", "publicKey", "privateKey"); + return new SshPairImpl("owner", "service", "name", "publicKey", "privateKey"); } } diff --git a/wsmaster/wsmaster-local/src/test/java/org/eclipse/che/api/local/LocalProfileTckRepository.java b/wsmaster/wsmaster-local/src/test/java/org/eclipse/che/api/local/LocalSshTckRepository.java similarity index 64% rename from wsmaster/wsmaster-local/src/test/java/org/eclipse/che/api/local/LocalProfileTckRepository.java rename to wsmaster/wsmaster-local/src/test/java/org/eclipse/che/api/local/LocalSshTckRepository.java index 86f9b38d80..b8c06c5639 100644 --- a/wsmaster/wsmaster-local/src/test/java/org/eclipse/che/api/local/LocalProfileTckRepository.java +++ b/wsmaster/wsmaster-local/src/test/java/org/eclipse/che/api/local/LocalSshTckRepository.java @@ -10,30 +10,30 @@ *******************************************************************************/ package org.eclipse.che.api.local; -import org.eclipse.che.api.user.server.model.impl.ProfileImpl; +import org.eclipse.che.api.ssh.server.model.impl.SshPairImpl; +import org.eclipse.che.commons.lang.Pair; import org.eclipse.che.commons.test.tck.repository.TckRepository; import org.eclipse.che.commons.test.tck.repository.TckRepositoryException; import javax.inject.Inject; import java.util.Collection; +import java.util.HashMap; +import java.util.Map; /** - * @author Yevhenii Voevodin + * @author Mihail Kuznyetsov */ -public class LocalProfileTckRepository implements TckRepository { - +public class LocalSshTckRepository implements TckRepository { @Inject - private LocalProfileDaoImpl profileDao; + private LocalSshDaoImpl sshDao; @Override - public void createAll(Collection profiles) throws TckRepositoryException { - for (ProfileImpl profile : profiles) { - profileDao.profiles.put(profile.getUserId(), new ProfileImpl(profile)); - } + public void createAll(Collection entities) throws TckRepositoryException { + sshDao.pairs.addAll(entities); } @Override public void removeAll() throws TckRepositoryException { - profileDao.profiles.clear(); + sshDao.pairs.clear(); } } diff --git a/wsmaster/wsmaster-local/src/test/java/org/eclipse/che/api/local/LocalStackDaoTest.java b/wsmaster/wsmaster-local/src/test/java/org/eclipse/che/api/local/LocalStackDaoTest.java index 3862d096be..69dda18098 100644 --- a/wsmaster/wsmaster-local/src/test/java/org/eclipse/che/api/local/LocalStackDaoTest.java +++ b/wsmaster/wsmaster-local/src/test/java/org/eclipse/che/api/local/LocalStackDaoTest.java @@ -94,7 +94,7 @@ public class LocalStackDaoTest { StackImpl stack = createStack(); stackDao.create(stack); - stackDao.stop(); + stackDao.saveStacks(); assertEquals(GSON.toJson(ImmutableMap.of("stackdskhfdskf", stack)), new String(readAllBytes(stackJsonPath))); //check icon content diff --git a/wsmaster/wsmaster-local/src/test/java/org/eclipse/che/api/local/LocalTckModule.java b/wsmaster/wsmaster-local/src/test/java/org/eclipse/che/api/local/LocalTckModule.java index 261e3932bd..9a9ac5cfa9 100644 --- a/wsmaster/wsmaster-local/src/test/java/org/eclipse/che/api/local/LocalTckModule.java +++ b/wsmaster/wsmaster-local/src/test/java/org/eclipse/che/api/local/LocalTckModule.java @@ -10,21 +10,41 @@ *******************************************************************************/ package org.eclipse.che.api.local; +import com.google.inject.Inject; import com.google.inject.TypeLiteral; import com.google.inject.name.Names; +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.local.storage.LocalStorage; import org.eclipse.che.api.local.storage.LocalStorageFactory; +import org.eclipse.che.api.local.storage.stack.StackLocalStorage; +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.model.impl.SshPairImpl; +import org.eclipse.che.api.ssh.server.spi.SshDao; 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.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.tck.TckModule; import org.eclipse.che.commons.test.tck.repository.TckRepository; import javax.inject.Singleton; import java.io.IOException; import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; import java.util.Set; import static java.util.Collections.emptySet; @@ -50,12 +70,103 @@ public class LocalTckModule extends TckModule { } bind(LocalStorageFactory.class).toInstance(factory); + // Configure stack local storage to deal with mocks + final StackLocalStorage stackStorage = mock(StackLocalStorage.class); + when(stackStorage.loadMap()).thenReturn(Collections.emptyMap()); + bind(StackLocalStorage.class).toInstance(stackStorage); + bind(new TypeLiteral>() {}).annotatedWith(Names.named("codenvy.local.infrastructure.users")).toInstance(emptySet()); - bind(new TypeLiteral>() {}).to(LocalUserTckRepository.class).in(Singleton.class); - bind(new TypeLiteral>() {}).to(LocalProfileTckRepository.class).in(Singleton.class); + bind(new TypeLiteral>() {}).to(LocalUserTckRepository.class); + bind(new TypeLiteral>() {}).to(LocalProfileTckRepository.class); + bind(new TypeLiteral>() {}).to(LocalRecipeTckRepository.class); + bind(new TypeLiteral>() {}).to(LocalWorkspaceTckRepository.class); + bind(new TypeLiteral>>>() {}).to(LocalPreferenceTckRepository.class); + bind(new TypeLiteral>() {}).to(LocalStackTckRepository.class); + bind(new TypeLiteral>() {}).to(SnapshotTckRepository.class); + bind(new TypeLiteral>() {}).to(LocalSshTckRepository.class); + bind(new TypeLiteral>() {}).to(LocalAccountTckRepository.class); - bind(UserDao.class).to(LocalUserDaoImpl.class).in(Singleton.class); - bind(ProfileDao.class).to(LocalProfileDaoImpl.class).in(Singleton.class); + bind(UserDao.class).to(LocalUserDaoImpl.class); + bind(ProfileDao.class).to(LocalProfileDaoImpl.class); + bind(RecipeDao.class).to(LocalRecipeDaoImpl.class); + bind(WorkspaceDao.class).to(LocalWorkspaceDaoImpl.class); + bind(PreferenceDao.class).to(LocalPreferenceDaoImpl.class); + bind(StackDao.class).to(LocalStackDaoImpl.class); + bind(SnapshotDao.class).to(LocalSnapshotDaoImpl.class); + bind(SshDao.class).to(LocalSshDaoImpl.class); + } + + @Singleton + private static class SnapshotTckRepository extends LocalMapTckRepository { + @Inject + public SnapshotTckRepository(LocalSnapshotDaoImpl snapshotDao) { + super(snapshotDao.snapshots, SnapshotImpl::getId, snapshotDao); + } + } + + @Singleton + private static class LocalUserTckRepository extends LocalMapTckRepository { + @Inject + public LocalUserTckRepository(LocalUserDaoImpl userDao) { + super(userDao.users, UserImpl::getId, userDao); + } + } + + @Singleton + private static class LocalProfileTckRepository extends LocalMapTckRepository { + @Inject + public LocalProfileTckRepository(LocalProfileDaoImpl profileDao) { + super(profileDao.profiles, ProfileImpl::getUserId, profileDao); + } + } + + @Singleton + private static class LocalRecipeTckRepository extends LocalMapTckRepository { + @Inject + public LocalRecipeTckRepository(LocalRecipeDaoImpl recipeDao) { + super(recipeDao.recipes, RecipeImpl::getId, recipeDao); + } + } + + @Singleton + private static class LocalWorkspaceTckRepository extends LocalMapTckRepository { + @Inject + public LocalWorkspaceTckRepository(LocalWorkspaceDaoImpl workspaceDao) { + super(workspaceDao.workspaces, WorkspaceImpl::getId, workspaceDao); + } + } + + @Singleton + private static class LocalStackTckRepository extends LocalMapTckRepository { + @Inject + public LocalStackTckRepository(LocalStackDaoImpl stackDao) { + super(stackDao.stacks, StackImpl::getId, stackDao); + } + } + + @Singleton + private static class LocalPreferenceTckRepository + extends LocalTckRepository>, Pair>> { + @Inject + public LocalPreferenceTckRepository(LocalPreferenceDaoImpl prefsDao) { + super(prefsDao.preferences, (map, entity) -> map.put(entity.first, entity.second), Map::clear, prefsDao); + } + } + + @Singleton + private static class LocalSshTckRepository extends LocalTckRepository, SshPairImpl> { + @Inject + public LocalSshTckRepository(LocalSshDaoImpl sshDao) { + super(sshDao.pairs, List::add, List::clear, sshDao); + } + } + + @Singleton + private static class LocalAccountTckRepository extends LocalMapTckRepository { + @Inject + public LocalAccountTckRepository() { + super(new HashMap<>(), AccountImpl::getId, null); + } } } diff --git a/wsmaster/wsmaster-local/src/test/java/org/eclipse/che/api/local/LocalTckRepository.java b/wsmaster/wsmaster-local/src/test/java/org/eclipse/che/api/local/LocalTckRepository.java new file mode 100644 index 0000000000..44042280e1 --- /dev/null +++ b/wsmaster/wsmaster-local/src/test/java/org/eclipse/che/api/local/LocalTckRepository.java @@ -0,0 +1,72 @@ +/******************************************************************************* + * Copyright (c) 2012-2016 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 + *******************************************************************************/ +package org.eclipse.che.api.local; + +import org.eclipse.che.commons.annotation.Nullable; +import org.eclipse.che.commons.test.tck.repository.TckRepository; +import org.eclipse.che.commons.test.tck.repository.TckRepositoryException; + +import java.util.Collection; +import java.util.function.BiConsumer; +import java.util.function.Consumer; + +import static java.util.Objects.requireNonNull; + +/** + * Simplifies implementation of TckRepository for local data access objects. + * + * @param + * the type of the storage + * @param + * the type of the entity + * @author Yevhenii Voevodin + */ +public class LocalTckRepository implements TckRepository { + + private final STORAGE_T storage; + private final BiConsumer adder; + private final Consumer cleaner; + private final Object mutex; + + public LocalTckRepository(STORAGE_T storage, + BiConsumer adder, + Consumer cleaner, + @Nullable Object mutex) { + this.storage = requireNonNull(storage, "Required non-null storage"); + this.adder = requireNonNull(adder, "Required non-null adder"); + this.cleaner = requireNonNull(cleaner, "Required non-null cleaner"); + this.mutex = mutex; + } + + @Override + public void createAll(Collection entities) throws TckRepositoryException { + withMutex(() -> { + for (ENTITY_T entity : entities) { + adder.accept(storage, entity); + } + }); + } + + @Override + public void removeAll() throws TckRepositoryException { + withMutex(() -> cleaner.accept(storage)); + } + + private void withMutex(Runnable action) { + if (mutex == null) { + action.run(); + } else { + synchronized (mutex) { + action.run(); + } + } + } +} diff --git a/wsmaster/wsmaster-local/src/test/java/org/eclipse/che/api/local/LocalWorkspaceDaoTest.java b/wsmaster/wsmaster-local/src/test/java/org/eclipse/che/api/local/LocalWorkspaceDaoTest.java index 26eda6f9e0..c09d6ee1d4 100644 --- a/wsmaster/wsmaster-local/src/test/java/org/eclipse/che/api/local/LocalWorkspaceDaoTest.java +++ b/wsmaster/wsmaster-local/src/test/java/org/eclipse/che/api/local/LocalWorkspaceDaoTest.java @@ -13,6 +13,7 @@ package org.eclipse.che.api.local; import com.google.gson.Gson; import com.google.gson.GsonBuilder; +import org.eclipse.che.account.spi.AccountImpl; import org.eclipse.che.api.local.storage.LocalStorageFactory; import org.eclipse.che.api.machine.server.model.impl.CommandImpl; import org.eclipse.che.api.workspace.server.WorkspaceConfigJsonAdapter; @@ -216,7 +217,7 @@ public class LocalWorkspaceDaoTest { commands, projects, environments)) - .setNamespace("user123") + .setAccount(new AccountImpl("accountId", "user123", "test")) .build(); } }