diff --git a/core/ide/che-core-ide-api/src/main/java/org/eclipse/che/ide/api/factory/FactoryServiceClient.java b/core/ide/che-core-ide-api/src/main/java/org/eclipse/che/ide/api/factory/FactoryServiceClient.java index d4cb88fa0ae..37e0a5a61ba 100644 --- a/core/ide/che-core-ide-api/src/main/java/org/eclipse/che/ide/api/factory/FactoryServiceClient.java +++ b/core/ide/che-core-ide-api/src/main/java/org/eclipse/che/ide/api/factory/FactoryServiceClient.java @@ -18,6 +18,7 @@ import javax.validation.constraints.NotNull; import java.util.List; +import java.util.Map; /** * Client for Factory service. @@ -33,10 +34,9 @@ public interface FactoryServiceClient { * factory ID to retrieve * @param validate * indicates whether or not factory should be validated by accept validator - * @param callback - * callback which return valid JSON object of factory or exception if occurred + * @return Factory through a Promise */ - void getFactory(@NotNull String factoryId, boolean validate, @NotNull AsyncRequestCallback callback); + Promise getFactory(@NotNull String factoryId, boolean validate); /** * @param factoryId @@ -101,4 +101,17 @@ public interface FactoryServiceClient { * @return updated factory */ Promise updateFactory(String id, Factory factory); + + + /** + * Resolve factory object based on user parameters + * + * @param factoryParameters + * map containing factory data parameters provided through URL + * @param validate + * indicates whether or not factory should be validated by accept validator + * @return Factory through a Promise + */ + Promise resolveFactory(@NotNull Map factoryParameters, boolean validate); + } diff --git a/core/ide/che-core-ide-api/src/main/java/org/eclipse/che/ide/api/factory/FactoryServiceClientImpl.java b/core/ide/che-core-ide-api/src/main/java/org/eclipse/che/ide/api/factory/FactoryServiceClientImpl.java index ad5e283477d..46b1500bcfd 100644 --- a/core/ide/che-core-ide-api/src/main/java/org/eclipse/che/ide/api/factory/FactoryServiceClientImpl.java +++ b/core/ide/che-core-ide-api/src/main/java/org/eclipse/che/ide/api/factory/FactoryServiceClientImpl.java @@ -30,6 +30,11 @@ import java.util.Collection; import java.util.LinkedList; import java.util.List; +import java.util.Map; + +import static org.eclipse.che.ide.MimeType.APPLICATION_JSON; +import static org.eclipse.che.ide.json.JsonHelper.toJson; +import static org.eclipse.che.ide.rest.HTTPHeader.ACCEPT; /** * Implementation of {@link FactoryServiceClient} service. @@ -54,16 +59,22 @@ public FactoryServiceClientImpl(AsyncRequestFactory asyncRequestFactory, } /** - * {@inheritDoc} + * Get valid JSON factory object based on input factory ID + * + * @param factoryId + * factory ID to retrieve + * @param validate + * indicates whether or not factory should be validated by accept validator + * @return Factory through a Promise */ @Override - public void getFactory(@NotNull String factoryId, boolean validate, @NotNull AsyncRequestCallback callback) { + 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"); } - asyncRequestFactory.createGetRequest(url.toString()).header(HTTPHeader.ACCEPT, MimeType.APPLICATION_JSON) - .send(callback); + return asyncRequestFactory.createGetRequest(url.toString()).header(HTTPHeader.ACCEPT, MimeType.APPLICATION_JSON) + .send(unmarshallerFactory.newUnmarshaller(Factory.class)); } /** @@ -141,6 +152,30 @@ public Promise updateFactory(String id, Factory factory) { .send(unmarshallerFactory.newUnmarshaller(Factory.class)); } + + /** + * Resolve factory object based on user parameters + * + * @param factoryParameters + * map containing factory data parameters provided through URL + * @param validate + * indicates whether or not factory should be validated by accept validator + * @return Factory through a Promise + */ + @Override + public Promise resolveFactory(@NotNull final Map factoryParameters, boolean validate) { + + // Init string with JAX-RS path + StringBuilder url = new StringBuilder(API_FACTORY_BASE_URL + "resolver"); + + // If validation, needs to add the flag + if (validate) { + url.append("?validate=true"); + } + return asyncRequestFactory.createPostRequest(url.toString(), toJson(factoryParameters)).header(ACCEPT, APPLICATION_JSON) + .send(unmarshallerFactory.newUnmarshaller(Factory.class)); + } + /** * Forms the query string from collection of query params * diff --git a/core/ide/che-core-ide-app/src/main/java/org/eclipse/che/ide/workspace/FactoryWorkspaceComponent.java b/core/ide/che-core-ide-app/src/main/java/org/eclipse/che/ide/workspace/FactoryWorkspaceComponent.java index 6adc514ce84..a879c28664b 100644 --- a/core/ide/che-core-ide-app/src/main/java/org/eclipse/che/ide/workspace/FactoryWorkspaceComponent.java +++ b/core/ide/che-core-ide-app/src/main/java/org/eclipse/che/ide/workspace/FactoryWorkspaceComponent.java @@ -17,26 +17,29 @@ import com.google.web.bindery.event.shared.EventBus; import org.eclipse.che.api.core.model.workspace.WorkspaceStatus; -import org.eclipse.che.ide.api.factory.FactoryServiceClient; import org.eclipse.che.api.factory.shared.dto.Factory; -import org.eclipse.che.ide.api.machine.MachineManager; +import org.eclipse.che.api.promises.client.Function; +import org.eclipse.che.api.promises.client.FunctionException; 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.ide.api.workspace.WorkspaceServiceClient; import org.eclipse.che.api.workspace.shared.dto.WorkspaceDto; import org.eclipse.che.ide.CoreLocalizationConstant; import org.eclipse.che.ide.actions.WorkspaceSnapshotCreator; import org.eclipse.che.ide.api.app.AppContext; import org.eclipse.che.ide.api.component.Component; +import org.eclipse.che.ide.api.dialogs.DialogFactory; +import org.eclipse.che.ide.api.factory.FactoryServiceClient; +import org.eclipse.che.ide.api.machine.MachineManager; import org.eclipse.che.ide.api.notification.NotificationManager; import org.eclipse.che.ide.api.preferences.PreferencesManager; +import org.eclipse.che.ide.api.workspace.WorkspaceServiceClient; +import org.eclipse.che.ide.collections.Jso; +import org.eclipse.che.ide.collections.js.JsoArray; import org.eclipse.che.ide.context.BrowserQueryFieldRenderer; import org.eclipse.che.ide.dto.DtoFactory; -import org.eclipse.che.ide.rest.AsyncRequestCallback; import org.eclipse.che.ide.rest.DtoUnmarshallerFactory; -import org.eclipse.che.ide.api.dialogs.DialogFactory; import org.eclipse.che.ide.ui.loaders.initialization.InitialLoadingInfo; import org.eclipse.che.ide.ui.loaders.initialization.LoaderPresenter; import org.eclipse.che.ide.util.loging.Log; @@ -44,6 +47,9 @@ import org.eclipse.che.ide.workspace.create.CreateWorkspacePresenter; import org.eclipse.che.ide.workspace.start.StartWorkspacePresenter; +import java.util.HashMap; +import java.util.Map; + import static org.eclipse.che.api.core.model.workspace.WorkspaceStatus.RUNNING; import static org.eclipse.che.ide.api.notification.StatusNotification.DisplayMode.FLOAT_MODE; import static org.eclipse.che.ide.api.notification.StatusNotification.Status.FAIL; @@ -56,8 +62,8 @@ */ @Singleton public class FactoryWorkspaceComponent extends WorkspaceComponent implements Component { - private final FactoryServiceClient factoryServiceClient; - private String workspaceId; + private final FactoryServiceClient factoryServiceClient; + private String workspaceId; @Inject public FactoryWorkspaceComponent(WorkspaceServiceClient workspaceServiceClient, @@ -101,27 +107,46 @@ public FactoryWorkspaceComponent(WorkspaceServiceClient workspaceServiceClient, @Override public void start(final Callback callback) { this.callback = callback; - String factoryParams = browserQueryFieldRenderer.getParameterFromURLByName("factory"); + Jso factoryParams = browserQueryFieldRenderer.getParameters(); + JsoArray keys = factoryParams.getKeys(); + Map factoryParameters = new HashMap<>(); + // check all factory parameters + for (String key : keys.toList()) { + if (key.startsWith("factory-")) { + String value = factoryParams.getStringField(key); + factoryParameters.put(key.substring("factory-".length()), value); + } + } // get workspace ID to use dedicated workspace for this factory this.workspaceId = browserQueryFieldRenderer.getParameterFromURLByName("workspaceId"); - factoryServiceClient.getFactory(factoryParams, true, - new AsyncRequestCallback(dtoUnmarshallerFactory.newUnmarshaller(Factory.class)) { - @Override - protected void onSuccess(Factory result) { - appContext.setFactory(result); - - // get workspace - tryStartWorkspace(); - } - - @Override - protected void onFailure(Throwable error) { - Log.error(FactoryWorkspaceComponent.class, "Unable to load Factory", error); - callback.onFailure(new Exception(error.getCause())); - } - }); + 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); + } else { + factoryPromise = factoryServiceClient.resolveFactory(factoryParameters, true); + } + + Promise promise = factoryPromise.then(new Function() { + @Override + public Void apply(final Factory factory) throws FunctionException { + appContext.setFactory(factory); + + // get workspace + tryStartWorkspace(); + + return null; + } + }).catchError(new Operation() { + @Override + public void apply(PromiseError error) throws OperationException { + Log.error(FactoryWorkspaceComponent.class, "Unable to load Factory", error); + callback.onFailure(new Exception(error.getCause())); + } + }); + } @Override diff --git a/wsmaster/che-core-api-factory/pom.xml b/wsmaster/che-core-api-factory/pom.xml index 14d5ed33ff4..d75805172d9 100644 --- a/wsmaster/che-core-api-factory/pom.xml +++ b/wsmaster/che-core-api-factory/pom.xml @@ -33,6 +33,10 @@ com.google.guava guava + + com.google.inject + guice + commons-fileupload commons-fileupload @@ -49,6 +53,10 @@ javax.inject javax.inject + + javax.validation + validation-api + org.eclipse.che.core che-core-api-core 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 new file mode 100644 index 00000000000..87984b9e9af --- /dev/null +++ b/wsmaster/che-core-api-factory/src/main/java/org/eclipse/che/api/factory/server/FactoryParametersResolver.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.factory.server; + +import org.eclipse.che.api.core.BadRequestException; +import org.eclipse.che.api.factory.shared.dto.Factory; + +import javax.validation.constraints.NotNull; +import java.util.Map; + +/** + * Defines a resolver that will produce factories for some parameters + * + * @author Florent Benoit + */ +public interface FactoryParametersResolver { + + /** + * Resolver acceptance based on the given parameters. + * + * @param factoryParameters + * map of parameters dedicated to factories + * @return true if it will be accepted by the resolver implementation or false if it is not accepted + */ + boolean accept(@NotNull Map factoryParameters); + + /** + * Create factory object based on provided parameters + * + * @param factoryParameters + * map containing factory data parameters provided through URL + * @throws BadRequestException + * when data are invalid + */ + Factory 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 f6e3548f7f4..dc9fa6c1291 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 @@ -63,9 +63,12 @@ import java.io.InputStream; import java.io.UnsupportedEncodingException; import java.util.Arrays; +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; @@ -82,6 +85,7 @@ * Defines Factory REST API. * * @author Anton Korneta + * @author Florent Benoit */ @Api(value = "/factory", description = "Factory manager") @@ -89,6 +93,26 @@ public class FactoryService extends Service { private static final Logger LOG = LoggerFactory.getLogger(FactoryService.class); + /** + * Error message if there is no plugged resolver. + */ + 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 + */ + public static final String VALIDATE_QUERY_PARAMETER = "validate"; + + /** + * Set of resolvers for factories. Injected through an holder. + */ + private final Set factoryParametersResolvers; + private final FactoryStore factoryStore; private final FactoryEditValidator factoryEditValidator; private final FactoryCreateValidator createValidator; @@ -106,6 +130,7 @@ public FactoryService(FactoryStore factoryStore, LinksHelper linksHelper, FactoryBuilder factoryBuilder, WorkspaceManager workspaceManager, + FactoryParametersResolverHolder factoryParametersResolverHolder, UserDao userDao) { this.factoryStore = factoryStore; this.createValidator = createValidator; @@ -114,6 +139,7 @@ public FactoryService(FactoryStore factoryStore, this.linksHelper = linksHelper; this.factoryBuilder = factoryBuilder; this.workspaceManager = workspaceManager; + this.factoryParametersResolvers = factoryParametersResolverHolder.getFactoryParametersResolvers(); this.userDao = userDao; } @@ -569,6 +595,74 @@ public Response getFactoryJson(@ApiParam(value = "Workspace ID") .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"), + @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 { + + // Check parameter + if (parameters == null) { + throw new BadRequestException(ERROR_NO_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. * @@ -639,4 +733,30 @@ private void processDefaults(Factory factory) { creator.setCreated(System.currentTimeMillis()); } } + + + /** + * Usage of a dedicated class to manage the optional resolvers + */ + protected static class FactoryParametersResolverHolder { + + /** + * Optional inject for the resolvers. + */ + @com.google.inject.Inject(optional = true) + private Set factoryParametersResolvers; + + /** + * Provides the set of resolvers if there are some else return an empty set. + * @return a non null set + */ + public Set getFactoryParametersResolvers() { + if (factoryParametersResolvers != null) { + return factoryParametersResolvers; + } else { + return Collections.emptySet(); + } + } + + } } 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 index 5fba0833823..66a2b50cfd8 100644 --- 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 @@ -95,41 +95,55 @@ public List createLinks(Factory factory, UriInfo uriInfo, String userName) // add path to factory service final UriBuilder factoryUriBuilder = baseUriBuilder.clone().path(FactoryService.class); final String factoryId = factory.getId(); - - // 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); + 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 @@ -146,17 +160,6 @@ public List createLinks(Factory factory, UriInfo uriInfo, String userName) links.add(createWorkspaceFromNamedFactory); } - // 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())); return links; } 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 f3ff1755057..12a610da431 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 @@ -13,6 +13,7 @@ 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.workspace.WorkspaceStatus; import org.eclipse.che.api.core.rest.ApiExceptionMapper; @@ -28,8 +29,8 @@ import org.eclipse.che.api.user.server.dao.User; import org.eclipse.che.api.user.server.dao.UserDao; import org.eclipse.che.api.workspace.server.WorkspaceManager; -import org.eclipse.che.api.workspace.server.model.impl.WorkspaceImpl; 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; @@ -67,23 +68,36 @@ 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 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 javax.ws.rs.core.Response.Status; +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.FactoryService.VALIDATE_QUERY_PARAMETER; import static org.eclipse.che.api.workspace.server.DtoConverter.asDto; import static org.hamcrest.Matchers.equalTo; import static org.mockito.Matchers.any; import static org.mockito.Matchers.anyInt; +import static org.mockito.Matchers.anyMap; 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.doThrow; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.never; import static org.mockito.Mockito.spy; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; @@ -93,12 +107,18 @@ @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(); + /** + * Path of the resolver REST service. + */ + private final String SERVICE_PATH_RESOLVER = SERVICE_PATH + "/resolver"; + private EnvironmentFilter filter = new EnvironmentFilter(); @Mock @@ -119,16 +139,28 @@ public class FactoryServiceTest { @Mock private UserDao userDao; + @Mock + private FactoryService.FactoryParametersResolverHolder factoryParametersResolverHolder; + private FactoryBuilder factoryBuilder; private FactoryService factoryService; private DtoFactory dto; + /** + * Set of all resolvers available for the factory service. + */ + @Mock + private Set factoryParametersResolvers; + + @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)); + when(factoryParametersResolverHolder.getFactoryParametersResolvers()).thenReturn(factoryParametersResolvers); when(userDao.getById(anyString())).thenReturn(new User().withName(JettyHttpServer.ADMIN_USER_NAME)); factoryService = new FactoryService(factoryStore, createValidator, @@ -137,6 +169,7 @@ public void setUp() throws Exception { new LinksHelper(), factoryBuilder, workspaceManager, + factoryParametersResolverHolder, userDao); } @@ -214,7 +247,7 @@ public void shouldReturnStatus400IfSaveRequestHaveNotFactoryInfo() throws Except .basic(JettyHttpServer.ADMIN_USER_NAME, JettyHttpServer.ADMIN_USER_PASSWORD) .multiPart("someOtherData", "Some content", MediaType.TEXT_PLAIN) .expect() - .statusCode(Status.BAD_REQUEST.getStatusCode()) + .statusCode(BAD_REQUEST.getStatusCode()) .when() .post("/private" + SERVICE_PATH); @@ -884,7 +917,7 @@ public void shouldNotBeAbleToUpdateANullFactory() throws Exception { .when() .put("/private" + SERVICE_PATH + "/" + ILLEGAL_FACTORY_ID); - assertEquals(response.getStatusCode(), Status.BAD_REQUEST.getStatusCode()); + assertEquals(response.getStatusCode(), BAD_REQUEST.getStatusCode()); assertEquals(dto.createDtoFromJson(response.getBody().asString(), ServiceError.class).getMessage(), "The updating factory shouldn't be null"); @@ -897,7 +930,7 @@ public void shouldResponse409OnGetSnippetIfTypeIsIllegal(String type) throws Exc // when, then Response response = given().expect() - .statusCode(Status.BAD_REQUEST.getStatusCode()) + .statusCode(BAD_REQUEST.getStatusCode()) .when() .get(SERVICE_PATH + "/" + CORRECT_FACTORY_ID + "/snippet?type=" + type); @@ -926,7 +959,7 @@ public void shouldNotFindWhenNoAttributesProvided() throws Exception { .when() .get("/private" + SERVICE_PATH + "/find"); // then - assertEquals(response.getStatusCode(), Status.BAD_REQUEST.getStatusCode()); + assertEquals(response.getStatusCode(), BAD_REQUEST.getStatusCode()); } @Test @@ -1076,7 +1109,7 @@ public void shouldThrowBadRequestExceptionWhenInvalidFactoryPost() throws Except .multiPart("image", path.toFile(), "image/jpeg") .when() .post("/private" + SERVICE_PATH); - assertEquals(response.getStatusCode(), Status.BAD_REQUEST.getStatusCode()); + assertEquals(response.getStatusCode(), BAD_REQUEST.getStatusCode()); } @Test @@ -1110,7 +1143,7 @@ public void shouldThrowBadRequestExceptionWhenTriedToStoreInvalidFactory() throw .contentType(ContentType.JSON) .body("") .post("/private" + SERVICE_PATH); - assertEquals(response.getStatusCode(), Status.BAD_REQUEST.getStatusCode()); + assertEquals(response.getStatusCode(), BAD_REQUEST.getStatusCode()); } @Test @@ -1217,7 +1250,172 @@ public void shouldNotGenerateFactoryIfNoProjectsWithSourceStorage() throws Excep .get("/private" + SERVICE_PATH + "/workspace/" + wsId); // then - assertEquals(response.getStatusCode(), Status.BAD_REQUEST.getStatusCode()); + 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()); + + Map map = new HashMap<>(); + // when + 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()); + 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()); + } + + /** + * Check that if there is a matching resolver and other not matching, factory is created + */ + @Test + public void onlyOneMatchingResolver() 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); + + // 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(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 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); + + // invalid factory + 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"); + + // 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).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()); + + // check we call validator + verify(acceptValidator).validateOnAccept(any()); + } private Factory prepareFactoryWithGivenStorage(String type, String location) {