/*******************************************************************************
* Copyright (c) 2012-2017 Codenvy, S.A.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* Codenvy, S.A. - initial API and implementation
*******************************************************************************/
package org.eclipse.che.ide.workspace.create;
import com.google.common.base.Strings;
import com.google.gwt.core.client.Callback;
import com.google.gwt.regexp.shared.RegExp;
import com.google.inject.Inject;
import com.google.inject.Provider;
import com.google.inject.Singleton;
import org.eclipse.che.api.machine.shared.dto.recipe.RecipeDescriptor;
import org.eclipse.che.api.promises.client.Operation;
import org.eclipse.che.api.promises.client.OperationException;
import org.eclipse.che.api.promises.client.Promise;
import org.eclipse.che.api.promises.client.PromiseError;
import org.eclipse.che.api.workspace.shared.dto.EnvironmentDto;
import org.eclipse.che.api.workspace.shared.dto.EnvironmentRecipeDto;
import org.eclipse.che.api.workspace.shared.dto.ExtendedMachineDto;
import org.eclipse.che.api.workspace.shared.dto.WorkspaceConfigDto;
import org.eclipse.che.api.workspace.shared.dto.WorkspaceDto;
import org.eclipse.che.ide.CoreLocalizationConstant;
import org.eclipse.che.ide.api.component.Component;
import org.eclipse.che.ide.api.machine.RecipeServiceClient;
import org.eclipse.che.ide.api.workspace.WorkspaceServiceClient;
import org.eclipse.che.ide.context.BrowserAddress;
import org.eclipse.che.ide.dto.DtoFactory;
import org.eclipse.che.ide.workspace.DefaultWorkspaceComponent;
import org.eclipse.che.ide.workspace.create.CreateWorkspaceView.HidePopupCallBack;
import java.util.ArrayList;
import java.util.List;
import static java.util.Collections.singletonList;
import static java.util.Collections.singletonMap;
import static org.eclipse.che.api.machine.shared.Constants.WS_MACHINE_NAME;
/**
* The class contains business logic which allow to create user workspace if it doesn't exist.
*
* @author Dmitry Shnurenko
*/
@Singleton
public class CreateWorkspacePresenter implements CreateWorkspaceView.ActionDelegate {
private static final RegExp FILE_NAME = RegExp.compile("^[A-Za-z0-9_\\s-\\.]+$");
private static final String URL_PATTERN = "^((ftp|http|https)://[\\w@.\\-\\_]+(:\\d{1,5})?(/[\\w#!:.?+=&%@!\\_\\-/]+)*){1}$";
private static final RegExp URL = RegExp.compile(URL_PATTERN);
protected static final String MEMORY_LIMIT_BYTES = Long.toString(2000L * 1024L * 1024L);
static final String RECIPE_TYPE = "docker";
static final int SKIP_COUNT = 0;
static final int MAX_COUNT = 100;
static final int MAX_NAME_LENGTH = 20;
static final int MIN_NAME_LENGTH = 3;
private final CreateWorkspaceView view;
private final DtoFactory dtoFactory;
private final WorkspaceServiceClient workspaceClient;
private final CoreLocalizationConstant locale;
private final Provider<DefaultWorkspaceComponent> wsComponentProvider;
private final RecipeServiceClient recipeService;
private final BrowserAddress browserAddress;
private Callback<Component, Exception> callback;
private List<RecipeDescriptor> recipes;
private List<String> workspacesNames;
@Inject
public CreateWorkspacePresenter(CreateWorkspaceView view,
DtoFactory dtoFactory,
WorkspaceServiceClient workspaceClient,
CoreLocalizationConstant locale,
Provider<DefaultWorkspaceComponent> wsComponentProvider,
RecipeServiceClient recipeService,
BrowserAddress browserAddress) {
this.view = view;
this.view.setDelegate(this);
this.dtoFactory = dtoFactory;
this.workspaceClient = workspaceClient;
this.locale = locale;
this.wsComponentProvider = wsComponentProvider;
this.recipeService = recipeService;
this.browserAddress = browserAddress;
this.workspacesNames = new ArrayList<>();
}
/**
* Shows special dialog window which allows set up workspace which will be created.
*
* @param workspaces
* list of existing workspaces
*/
public void show(List<WorkspaceDto> workspaces, final Callback<Component, Exception> callback) {
this.callback = callback;
workspacesNames.clear();
for (WorkspaceDto workspace : workspaces) {
workspacesNames.add(workspace.getConfig().getName());
}
Promise<List<RecipeDescriptor>> recipes = recipeService.getAllRecipes();
recipes.then(new Operation<List<RecipeDescriptor>>() {
@Override
public void apply(List<RecipeDescriptor> recipeDescriptors) throws OperationException {
CreateWorkspacePresenter.this.recipes = recipeDescriptors;
}
});
String workspaceName = browserAddress.getWorkspaceName();
view.setWorkspaceName(workspaceName);
validateCreateWorkspaceForm();
view.show();
}
private void validateCreateWorkspaceForm() {
String workspaceName = view.getWorkspaceName();
int nameLength = workspaceName.length();
String errorDescription = "";
boolean nameLengthIsInCorrect = nameLength < MIN_NAME_LENGTH || nameLength > MAX_NAME_LENGTH;
if (nameLengthIsInCorrect) {
errorDescription = locale.createWsNameLengthIsNotCorrect();
}
boolean nameIsInCorrect = !FILE_NAME.test(workspaceName);
if (nameIsInCorrect) {
errorDescription = locale.createWsNameIsNotCorrect();
}
boolean nameAlreadyExist = workspacesNames.contains(workspaceName);
if (nameAlreadyExist) {
errorDescription = locale.createWsNameAlreadyExist();
}
view.showValidationNameError(errorDescription);
String recipeUrl = view.getRecipeUrl();
boolean urlIsIncorrect = !Strings.isNullOrEmpty(recipeUrl) && !URL.test(recipeUrl) ;
view.setVisibleUrlError(urlIsIncorrect);
view.setEnableCreateButton(!urlIsIncorrect && errorDescription.isEmpty());
}
/** {@inheritDoc} */
@Override
public void onNameChanged() {
validateCreateWorkspaceForm();
}
/** {@inheritDoc} */
@Override
public void onRecipeUrlChanged() {
validateCreateWorkspaceForm();
}
/** {@inheritDoc} */
@Override
public void onTagsChanged(final HidePopupCallBack callBack) {
recipeService.searchRecipes(view.getTags(), RECIPE_TYPE, SKIP_COUNT, MAX_COUNT).then(new Operation<List<RecipeDescriptor>>() {
@Override
public void apply(List<RecipeDescriptor> recipes) throws OperationException {
boolean isRecipesEmpty = recipes.isEmpty();
if (isRecipesEmpty) {
callBack.hidePopup();
} else {
view.showFoundByTagRecipes(recipes);
}
view.setVisibleTagsError(isRecipesEmpty);
}
});
}
/** {@inheritDoc} */
@Override
public void onPredefinedRecipesClicked() {
view.showPredefinedRecipes(recipes);
}
/** {@inheritDoc} */
@Override
public void onCreateButtonClicked() {
view.hide();
createWorkspace();
}
private void createWorkspace() {
WorkspaceConfigDto workspaceConfig = getWorkspaceConfig();
workspaceClient.create(workspaceConfig, null).then(new Operation<WorkspaceDto>() {
@Override
public void apply(WorkspaceDto workspace) throws OperationException {
DefaultWorkspaceComponent component = wsComponentProvider.get();
component.startWorkspace(workspace, callback);
}
}).catchError(new Operation<PromiseError>() {
@Override
public void apply(PromiseError arg) throws OperationException {
callback.onFailure(new Exception(arg.getCause()));
}
});
}
private WorkspaceConfigDto getWorkspaceConfig() {
String wsName = view.getWorkspaceName();
EnvironmentRecipeDto recipe = dtoFactory.createDto(EnvironmentRecipeDto.class)
.withType("dockerimage")
.withLocation(view.getRecipeUrl());
ExtendedMachineDto machine = dtoFactory.createDto(ExtendedMachineDto.class)
.withAgents(singletonList("org.eclipse.che.ws-agent"))
.withAttributes(singletonMap("memoryLimitBytes", MEMORY_LIMIT_BYTES));
EnvironmentDto environment = dtoFactory.createDto(EnvironmentDto.class)
.withRecipe(recipe)
.withMachines(singletonMap(WS_MACHINE_NAME, machine));
return dtoFactory.createDto(WorkspaceConfigDto.class)
.withName(wsName)
.withDefaultEnv(wsName)
.withEnvironments(singletonMap(wsName, environment));
}
}