/******************************************************************************* * 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.api.project; import com.google.gwt.http.client.URL; import com.google.inject.Inject; import org.eclipse.che.api.project.shared.dto.CopyOptions; import org.eclipse.che.api.project.shared.dto.ItemReference; import org.eclipse.che.api.project.shared.dto.MoveOptions; import org.eclipse.che.api.project.shared.dto.SourceEstimation; import org.eclipse.che.api.project.shared.dto.TreeElement; import org.eclipse.che.api.promises.client.Promise; import org.eclipse.che.api.workspace.shared.dto.NewProjectConfigDto; import org.eclipse.che.api.workspace.shared.dto.ProjectConfigDto; import org.eclipse.che.api.workspace.shared.dto.SourceStorageDto; import org.eclipse.che.ide.MimeType; import org.eclipse.che.ide.api.app.AppContext; import org.eclipse.che.ide.api.machine.WsAgentStateController; import org.eclipse.che.ide.dto.DtoFactory; import org.eclipse.che.ide.resource.Path; import org.eclipse.che.ide.rest.AsyncRequestFactory; import org.eclipse.che.ide.rest.DtoUnmarshallerFactory; import org.eclipse.che.ide.rest.StringUnmarshaller; import org.eclipse.che.ide.rest.UrlBuilder; import org.eclipse.che.ide.ui.loaders.request.LoaderFactory; import org.eclipse.che.ide.websocket.Message; import org.eclipse.che.ide.websocket.MessageBuilder; import org.eclipse.che.ide.websocket.WebSocketException; import org.eclipse.che.ide.websocket.rest.RequestCallback; import java.util.List; import java.util.Map; import static com.google.common.base.Strings.isNullOrEmpty; import static com.google.gwt.http.client.RequestBuilder.DELETE; import static com.google.gwt.http.client.RequestBuilder.POST; import static com.google.gwt.http.client.RequestBuilder.PUT; import static org.eclipse.che.api.promises.client.callback.AsyncPromiseHelper.createFromAsyncRequest; import static org.eclipse.che.ide.MimeType.APPLICATION_JSON; import static org.eclipse.che.ide.rest.HTTPHeader.ACCEPT; import static org.eclipse.che.ide.rest.HTTPHeader.CONTENTTYPE; import static org.eclipse.che.ide.rest.HTTPHeader.CONTENT_TYPE; /** * Implementation of {@link ProjectServiceClient}. * * TODO need to remove interface as this component is internal one and couldn't have more than one instance * * @author Vitaly Parfonov * @author Artem Zatsarynnyi * @author Valeriy Svydenko * @see ProjectServiceClient */ public class ProjectServiceClientImpl implements ProjectServiceClient { private static final String PROJECT = "/project"; private static final String BATCH_PROJECTS = "/batch"; private static final String ITEM = "/item"; private static final String TREE = "/tree"; private static final String MOVE = "/move"; private static final String COPY = "/copy"; private static final String FOLDER = "/folder"; private static final String FILE = "/file"; private static final String SEARCH = "/search"; private static final String IMPORT = "/import"; private static final String RESOLVE = "/resolve"; private static final String ESTIMATE = "/estimate"; private final WsAgentStateController wsAgentStateController; private final LoaderFactory loaderFactory; private final AsyncRequestFactory reqFactory; private final DtoFactory dtoFactory; private final DtoUnmarshallerFactory unmarshaller; private final AppContext appContext; @Inject protected ProjectServiceClientImpl(WsAgentStateController wsAgentStateController, LoaderFactory loaderFactory, AsyncRequestFactory reqFactory, DtoFactory dtoFactory, DtoUnmarshallerFactory unmarshaller, AppContext appContext) { this.wsAgentStateController = wsAgentStateController; this.loaderFactory = loaderFactory; this.reqFactory = reqFactory; this.dtoFactory = dtoFactory; this.unmarshaller = unmarshaller; this.appContext = appContext; } /** {@inheritDoc} */ @Override public Promise<List<ProjectConfigDto>> getProjects() { final String url = getBaseUrl(); return reqFactory.createGetRequest(url) .header(ACCEPT, MimeType.APPLICATION_JSON) .loader(loaderFactory.newLoader("Getting projects...")) .send(unmarshaller.newListUnmarshaller(ProjectConfigDto.class)); } /** {@inheritDoc} */ @Override public Promise<SourceEstimation> estimate(Path path, String pType) { final String url = getBaseUrl() + ESTIMATE + path(path.toString()) + "?type=" + pType; return reqFactory.createGetRequest(url) .header(ACCEPT, MimeType.APPLICATION_JSON) .loader(loaderFactory.newLoader("Estimating project...")) .send(unmarshaller.newUnmarshaller(SourceEstimation.class)); } /** {@inheritDoc} */ @Override public Promise<List<SourceEstimation>> resolveSources(Path path) { final String url = getBaseUrl() + RESOLVE + path(path.toString()); return reqFactory.createGetRequest(url) .header(ACCEPT, MimeType.APPLICATION_JSON) .loader(loaderFactory.newLoader("Resolving sources...")) .send(unmarshaller.newListUnmarshaller(SourceEstimation.class)); } /** {@inheritDoc} */ @Override public Promise<Void> importProject(final Path path, final SourceStorageDto source) { return createFromAsyncRequest(callback -> { final String url = PROJECT + IMPORT + path(path.toString()); final Message message = new MessageBuilder(POST, url).data(dtoFactory.toJson(source)) .header(CONTENTTYPE, APPLICATION_JSON) .build(); wsAgentStateController.getMessageBus().then(messageBus -> { try { messageBus.send(message, new RequestCallback<Void>() { @Override protected void onSuccess(Void result) { callback.onSuccess(result); } @Override protected void onFailure(Throwable exception) { callback.onFailure(exception); } }); } catch (WebSocketException e) { callback.onFailure(e); } }).catchError(error -> { callback.onFailure(error.getCause()); }); }); } /** {@inheritDoc} */ @Override public Promise<List<ItemReference>> search(QueryExpression expression) { final String url = getBaseUrl() + SEARCH + (isNullOrEmpty(expression.getPath()) ? Path.ROOT : path(expression.getPath())); StringBuilder queryParameters = new StringBuilder(); if (expression.getName() != null && !expression.getName().isEmpty()) { queryParameters.append("&name=").append(expression.getName()); } if (expression.getText() != null && !expression.getText().isEmpty()) { queryParameters.append("&text=").append(expression.getText()); } if (expression.getMaxItems() != 0) { queryParameters.append("&maxItems=").append(expression.getMaxItems()); } if (expression.getSkipCount() != 0) { queryParameters.append("&skipCount=").append(expression.getSkipCount()); } return reqFactory.createGetRequest(url + queryParameters.toString().replaceFirst("&", "?")) .header(ACCEPT, MimeType.APPLICATION_JSON) .loader(loaderFactory.newLoader("Searching...")) .send(unmarshaller.newListUnmarshaller(ItemReference.class)); } /** {@inheritDoc} */ @Override public Promise<ProjectConfigDto> createProject(ProjectConfigDto configuration, Map<String, String> options) { UrlBuilder urlBuilder = new UrlBuilder(getBaseUrl()); for(String key : options.keySet()) { urlBuilder.setParameter(key, options.get(key)); } return reqFactory.createPostRequest(urlBuilder.buildString(), configuration) .header(ACCEPT, MimeType.APPLICATION_JSON) .loader(loaderFactory.newLoader("Creating project...")) .send(unmarshaller.newUnmarshaller(ProjectConfigDto.class)); } @Override public Promise<List<ProjectConfigDto>> createBatchProjects(List<NewProjectConfigDto> configurations) { final String url = getBaseUrl() + BATCH_PROJECTS; final String loaderMessage = configurations.size() > 1 ? "Creating the batch of projects..." : "Creating project..."; return reqFactory.createPostRequest(url, configurations) .header(ACCEPT, MimeType.APPLICATION_JSON) .loader(loaderFactory.newLoader(loaderMessage)) .send(unmarshaller.newListUnmarshaller(ProjectConfigDto.class)); } /** {@inheritDoc} */ @Override public Promise<ItemReference> createFile(Path path, String content) { final String url = getBaseUrl() + FILE + path(path.parent().toString()) + "?name=" + URL.encodeQueryString(path.lastSegment()); return reqFactory.createPostRequest(url, null) .data(content) .loader(loaderFactory.newLoader("Creating file...")) .send(unmarshaller.newUnmarshaller(ItemReference.class)); } /** {@inheritDoc} */ @Override public Promise<String> getFileContent(Path path) { final String url = getBaseUrl() + FILE + path(path.toString()); return reqFactory.createGetRequest(url) .loader(loaderFactory.newLoader("Loading file content...")) .send(new StringUnmarshaller()); } /** {@inheritDoc} */ @Override public Promise<Void> setFileContent(Path path, String content) { final String url = getBaseUrl() + FILE + path(path.toString()); return reqFactory.createRequest(PUT, url, null, false) .data(content) .loader(loaderFactory.newLoader("Updating file...")) .send(); } /** {@inheritDoc} */ @Override public Promise<ItemReference> createFolder(Path path) { final String url = getBaseUrl() + FOLDER + path(path.toString()); return reqFactory.createPostRequest(url, null) .loader(loaderFactory.newLoader("Creating folder...")) .send(unmarshaller.newUnmarshaller(ItemReference.class)); } /** {@inheritDoc} */ @Override public Promise<Void> deleteItem(Path path) { final String url = getBaseUrl() + path(path.toString()); return reqFactory.createRequest(DELETE, url, null, false) .loader(loaderFactory.newLoader("Deleting project...")) .send(); } /** {@inheritDoc} */ @Override public Promise<Void> copy(Path source, Path target, String newName, boolean overwrite) { final String url = getBaseUrl() + COPY + path(source.toString()) + "?to=" + URL.encodeQueryString(target.toString()); final CopyOptions copyOptions = dtoFactory.createDto(CopyOptions.class); copyOptions.setName(newName); copyOptions.setOverWrite(overwrite); return reqFactory.createPostRequest(url, copyOptions) .loader(loaderFactory.newLoader("Copying...")) .send(); } /** {@inheritDoc} */ @Override public Promise<Void> move(Path source, Path target, String newName, boolean overwrite) { final String url = getBaseUrl() + MOVE + path(source.toString()) + "?to=" + URL.encodeQueryString(target.toString()); final MoveOptions moveOptions = dtoFactory.createDto(MoveOptions.class); moveOptions.setName(newName); moveOptions.setOverWrite(overwrite); return reqFactory.createPostRequest(url, moveOptions) .loader(loaderFactory.newLoader("Moving...")) .send(); } /** {@inheritDoc} */ @Override public Promise<TreeElement> getTree(Path path, int depth, boolean includeFiles) { final String url = getBaseUrl() + TREE + path(path.toString()) + "?depth=" + depth + "&includeFiles=" + includeFiles; // temporary workaround for CHE-3467, remove loader for disable UI blocking // later this loader should be added with the new mechanism of client-server synchronization return reqFactory.createGetRequest(url) .header(ACCEPT, MimeType.APPLICATION_JSON) .send(unmarshaller.newUnmarshaller(TreeElement.class)); } /** {@inheritDoc} */ @Override public Promise<ItemReference> getItem(Path path) { final String url = getBaseUrl() + ITEM + path(path.toString()); return reqFactory.createGetRequest(url) .header(ACCEPT, MimeType.APPLICATION_JSON) .loader(loaderFactory.newLoader("Getting item...")) .send(unmarshaller.newUnmarshaller(ItemReference.class)); } /** {@inheritDoc} */ @Override public Promise<ProjectConfigDto> getProject(Path path) { final String url = getBaseUrl() + path(path.toString()); return reqFactory.createGetRequest(url) .header(ACCEPT, MimeType.APPLICATION_JSON) .loader(loaderFactory.newLoader("Getting project...")) .send(unmarshaller.newUnmarshaller(ProjectConfigDto.class)); } /** {@inheritDoc} */ @Override public Promise<ProjectConfigDto> updateProject(ProjectConfigDto configuration) { final String url = getBaseUrl() + path(configuration.getPath()); return reqFactory.createRequest(PUT, url, configuration, false) .header(CONTENT_TYPE, MimeType.APPLICATION_JSON) .header(ACCEPT, MimeType.APPLICATION_JSON) .loader(loaderFactory.newLoader("Updating project...")) .send(unmarshaller.newUnmarshaller(ProjectConfigDto.class)); } /** * Returns the base url for the project service. It consists of workspace agent base url plus project prefix. * * @return base url for project service * @since 4.4.0 */ private String getBaseUrl() { return appContext.getDevMachine().getWsAgentBaseUrl() + PROJECT; } /** * Normalizes the path by adding a leading '/' if it doesn't exist. * Also escapes some special characters. * <p/> * See following javascript functions for details: * escape() will not encode: @ * / + * encodeURI() will not encode: ~ ! @ # $ & * ( ) = : / , ; ? + ' * encodeURIComponent() will not encode: ~ ! * ( ) ' * * @param path * path to normalize * @return normalized path */ private String path(String path) { while (path.indexOf('+') >= 0) { path = path.replace("+", "%2B"); } return path.startsWith("/") ? path : '/' + path; } }