From 77d88be882931b21990b623aeca055ba79f17a58 Mon Sep 17 00:00:00 2001 From: Libor Rysavy Date: Fri, 3 Dec 2021 17:07:18 +0100 Subject: [PATCH 1/2] WIP --- .../gooddata/sdk/model/workspace/Role.java | 125 ++++ .../gooddata/sdk/model/workspace/Roles.java | 37 ++ .../gooddata/sdk/model/workspace/User.java | 191 ++++++ .../gooddata/sdk/model/workspace/Users.java | 42 ++ .../model/workspace/UsersDeserializer.java | 24 + .../sdk/model/workspace/UsersSerializer.java | 35 ++ .../sdk/model/workspace/Workspace.java | 528 ++++++++++++++++ .../sdk/model/workspace/Workspaces.java | 60 ++ .../workspace/RoleNotFoundException.java | 25 + .../UserInWorkspaceNotFoundException.java | 22 + .../workspace/WorkspaceNotFoundException.java | 25 + .../service/workspace/WorkspaceService.java | 577 ++++++++++++++++++ .../WorkspaceUsersUpdateException.java | 22 + 13 files changed, 1713 insertions(+) create mode 100644 gooddata-java-model/src/main/java/com/gooddata/sdk/model/workspace/Role.java create mode 100644 gooddata-java-model/src/main/java/com/gooddata/sdk/model/workspace/Roles.java create mode 100644 gooddata-java-model/src/main/java/com/gooddata/sdk/model/workspace/User.java create mode 100644 gooddata-java-model/src/main/java/com/gooddata/sdk/model/workspace/Users.java create mode 100644 gooddata-java-model/src/main/java/com/gooddata/sdk/model/workspace/UsersDeserializer.java create mode 100644 gooddata-java-model/src/main/java/com/gooddata/sdk/model/workspace/UsersSerializer.java create mode 100644 gooddata-java-model/src/main/java/com/gooddata/sdk/model/workspace/Workspace.java create mode 100644 gooddata-java-model/src/main/java/com/gooddata/sdk/model/workspace/Workspaces.java create mode 100644 gooddata-java/src/main/java/com/gooddata/sdk/service/workspace/RoleNotFoundException.java create mode 100644 gooddata-java/src/main/java/com/gooddata/sdk/service/workspace/UserInWorkspaceNotFoundException.java create mode 100644 gooddata-java/src/main/java/com/gooddata/sdk/service/workspace/WorkspaceNotFoundException.java create mode 100644 gooddata-java/src/main/java/com/gooddata/sdk/service/workspace/WorkspaceService.java create mode 100644 gooddata-java/src/main/java/com/gooddata/sdk/service/workspace/WorkspaceUsersUpdateException.java diff --git a/gooddata-java-model/src/main/java/com/gooddata/sdk/model/workspace/Role.java b/gooddata-java-model/src/main/java/com/gooddata/sdk/model/workspace/Role.java new file mode 100644 index 000000000..8ff9452e2 --- /dev/null +++ b/gooddata-java-model/src/main/java/com/gooddata/sdk/model/workspace/Role.java @@ -0,0 +1,125 @@ +/* + * Copyright (C) 2004-2021, GoodData(R) Corporation. All rights reserved. + * This source code is licensed under the BSD-style license found in the + * LICENSE.txt file in the root directory of this source tree. + */ +package com.gooddata.sdk.model.workspace; + +import com.fasterxml.jackson.annotation.*; +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; +import com.fasterxml.jackson.databind.annotation.JsonSerialize; +import com.gooddata.sdk.model.md.Meta; +import com.gooddata.sdk.common.util.BooleanDeserializer; +import com.gooddata.sdk.common.util.BooleanStringSerializer; +import com.gooddata.sdk.common.util.GoodDataToStringBuilder; + +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; +import java.util.Map.Entry; +import java.util.Set; +import java.util.stream.Collectors; + +/** + * Workspace Role + */ +@JsonTypeInfo(include = JsonTypeInfo.As.WRAPPER_OBJECT, use = JsonTypeInfo.Id.NAME) +@JsonTypeName("projectRole") +@JsonIgnoreProperties(ignoreUnknown = true) +@JsonInclude(JsonInclude.Include.NON_NULL) +public class Role { + public static final String URI = "/gdc/projects/{workspaceId}/roles/{roleId}"; + + private static final String SELF_LINK = "self"; + + @JsonDeserialize(contentUsing = BooleanDeserializer.class) + @JsonSerialize(contentUsing = BooleanStringSerializer.class) + private final Map permissions; + + private final Meta meta; + + private final Map links; + + @JsonCreator + Role(@JsonProperty("permissions") final Map permissions, + @JsonProperty("meta") final Meta meta, + @JsonProperty("links") final Map links) { + this.permissions = permissions == null ? new HashMap<>() : permissions; + this.meta = meta == null ? new Meta(null) : meta; + this.links = links == null ? new HashMap<>() : links; + } + + /** + * Returns set of permission names this role can have granted. + * + * @return set of permission names + */ + public Set getPermissions() { + return Collections.unmodifiableSet(permissions.keySet()); + } + + /** + * Returns names of granted permissions. + * + * @return set of granted permissions + */ + public Set getGrantedPermissions() { + return permissions.entrySet().stream().filter(Entry::getValue).map(Entry::getKey).collect(Collectors.toSet()); + } + + /** + * Returns true if provided permission is granted. + * + * @param permission permission name to test + * @return whether the permission is granted + */ + public boolean hasPermissionGranted(final String permission) { + return permissions.get(permission); + } + + public String getTitle() { + return meta.getTitle(); + } + + public String getIdentifier() { + return meta.getIdentifier(); + } + + /** + * Allows service to set self link as it is not provided by REST API. + *

+ * NOTE: This is intentionally left package-private. + */ + public void setUri(final String uri) { + links.put(SELF_LINK, uri); + } + + @JsonIgnore + public String getUri() { + return links.get(SELF_LINK); + } + + @Override + public boolean equals(final Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + + final Role role = (Role) o; + + if (!permissions.equals(role.permissions)) return false; + return !(getIdentifier() != null ? !getIdentifier().equals(role.getIdentifier()) : role.getIdentifier() != null); + + } + + @Override + public int hashCode() { + int result = permissions.hashCode(); + result = 31 * result + (getIdentifier() != null ? getIdentifier().hashCode() : 0); + return result; + } + + @Override + public String toString() { + return GoodDataToStringBuilder.defaultToString(this); + } +} diff --git a/gooddata-java-model/src/main/java/com/gooddata/sdk/model/workspace/Roles.java b/gooddata-java-model/src/main/java/com/gooddata/sdk/model/workspace/Roles.java new file mode 100644 index 000000000..b5bff898c --- /dev/null +++ b/gooddata-java-model/src/main/java/com/gooddata/sdk/model/workspace/Roles.java @@ -0,0 +1,37 @@ +/* + * Copyright (C) 2004-2021, GoodData(R) Corporation. All rights reserved. + * This source code is licensed under the BSD-style license found in the + * LICENSE.txt file in the root directory of this source tree. + */ +package com.gooddata.sdk.model.workspace; + +import com.fasterxml.jackson.annotation.*; +import com.gooddata.sdk.common.util.GoodDataToStringBuilder; + +import java.util.Set; + +/** + * List of Role URIs. Deserialization only. + */ +@JsonTypeInfo(include = JsonTypeInfo.As.WRAPPER_OBJECT, use = JsonTypeInfo.Id.NAME) +@JsonTypeName("projectRoles") +@JsonIgnoreProperties(ignoreUnknown = true) +public class Roles { + public static final String URI = "/gdc/projects/{workspaceId}/roles"; + + private final Set roles; + + @JsonCreator + Roles(@JsonProperty("roles") final Set roles) { + this.roles = roles; + } + + public Set getRoles() { + return roles; + } + + @Override + public String toString() { + return GoodDataToStringBuilder.defaultToString(this); + } +} diff --git a/gooddata-java-model/src/main/java/com/gooddata/sdk/model/workspace/User.java b/gooddata-java-model/src/main/java/com/gooddata/sdk/model/workspace/User.java new file mode 100644 index 000000000..822904422 --- /dev/null +++ b/gooddata-java-model/src/main/java/com/gooddata/sdk/model/workspace/User.java @@ -0,0 +1,191 @@ +/* + * Copyright (C) 2004-2021, GoodData(R) Corporation. All rights reserved. + * This source code is licensed under the BSD-style license found in the + * LICENSE.txt file in the root directory of this source tree. + */ +package com.gooddata.sdk.model.workspace; + +import com.fasterxml.jackson.annotation.*; +import com.gooddata.sdk.model.account.Account; +import com.gooddata.sdk.common.util.GoodDataToStringBuilder; + +import java.util.Arrays; +import java.util.List; +import java.util.stream.Collectors; + +/** + * User in workspace + * @see Account + */ +@JsonTypeInfo(include = JsonTypeInfo.As.WRAPPER_OBJECT, use = JsonTypeInfo.Id.NAME) +@JsonTypeName("user") +@JsonIgnoreProperties(ignoreUnknown = true) +@JsonInclude(JsonInclude.Include.NON_NULL) +public class User { + + public static final String URI = "/gdc/projects/{workspaceId}/users/{userId}"; + + @JsonProperty + private UserContent content; + + private Links links; + + @JsonCreator + User(@JsonProperty("content") final UserContent content, + @JsonProperty("links") final Links links) { + this.content = content; + this.links = links; + } + + public User(final Account account, + final Role... userRoles) { + final List userRoleUris = Arrays.asList(userRoles).stream().map(e -> e.getUri()).collect(Collectors.toList()); + + links = new Links(account.getUri()); + content = new UserContent("ENABLED", userRoleUris); + } + + @JsonIgnore + public String getEmail() { + return content.getEmail(); + } + + @JsonIgnore + public String getStatus() { + return content.getStatus(); + } + + @JsonIgnore + public String getLastName() { + return content.getLastName(); + } + + @JsonIgnore + public List getUserRoles() { + return content.getUserRoles(); + } + + @JsonIgnore + public String getLogin() { + return content.getLogin(); + } + + @JsonIgnore + public String getFirstName() { + return content.getFirstName(); + } + + @JsonIgnore + public String getPhoneNumber() { + return content.getPhoneNumber(); + } + + public Links getLinks() { + return links; + } + + public void setStatus(final String status) { + this.content.status = status; + } + + @Override + public String toString() { + return GoodDataToStringBuilder.defaultToString(this); + } + + @JsonIgnoreProperties(ignoreUnknown = true) + @JsonInclude(JsonInclude.Include.NON_NULL) + private static class UserContent { + + @JsonProperty("email") + private String email; + + @JsonProperty("firstname") + private String firstName; + + @JsonProperty("userRoles") + private List userRoles; + + @JsonProperty("phonenumber") + private String phoneNumber; + + @JsonProperty("status") + private String status; + + @JsonProperty("lastname") + private String lastName; + + @JsonProperty("login") + private String login; + + @JsonCreator + public UserContent(@JsonProperty("email") final String email, + @JsonProperty("firstname") final String firstName, + @JsonProperty("userRoles") final List userRoles, + @JsonProperty("phonenumber") final String phoneNumber, + @JsonProperty("status") final String status, + @JsonProperty("lastname") final String lastName, + @JsonProperty("login") final String login) { + this.email = email; + this.firstName = firstName; + this.userRoles = userRoles; + this.phoneNumber = phoneNumber; + this.status = status; + this.lastName = lastName; + this.login = login; + } + + private UserContent(final String status, final List userRoles) { + this.userRoles = userRoles; + this.status = status; + } + + public String getEmail() { + return email; + } + + public String getFirstName() { + return firstName; + } + + public List getUserRoles() { + return userRoles; + } + + public String getPhoneNumber() { + return phoneNumber; + } + + public String getStatus() { + return status; + } + + public String getLastName() { + return lastName; + } + + public String getLogin() { + return login; + } + + @Override + public String toString() { + return GoodDataToStringBuilder.defaultToString(this); + } + } + + @JsonIgnoreProperties(ignoreUnknown = true) + @JsonInclude(JsonInclude.Include.NON_NULL) + private static class Links { + + private String self; + + private Links(@JsonProperty("self") final String self) { + this.self = self; + } + + public String getSelf() { + return self; + } + } +} diff --git a/gooddata-java-model/src/main/java/com/gooddata/sdk/model/workspace/Users.java b/gooddata-java-model/src/main/java/com/gooddata/sdk/model/workspace/Users.java new file mode 100644 index 000000000..091258c56 --- /dev/null +++ b/gooddata-java-model/src/main/java/com/gooddata/sdk/model/workspace/Users.java @@ -0,0 +1,42 @@ +/* + * Copyright (C) 2004-2021, GoodData(R) Corporation. All rights reserved. + * This source code is licensed under the BSD-style license found in the + * LICENSE.txt file in the root directory of this source tree. + */ +package com.gooddata.sdk.model.workspace; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonTypeInfo; +import com.fasterxml.jackson.annotation.JsonTypeInfo.Id; +import com.fasterxml.jackson.annotation.JsonTypeName; +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; +import com.fasterxml.jackson.databind.annotation.JsonSerialize; +import com.gooddata.sdk.common.collections.Page; +import com.gooddata.sdk.common.collections.Paging; + +import java.util.List; + +import static com.gooddata.sdk.model.workspace.Users.ROOT_NODE; +import static java.util.Arrays.asList; + +/** + * List of users. + */ +@JsonTypeInfo(include = JsonTypeInfo.As.WRAPPER_OBJECT, use = Id.NONE) +@JsonTypeName(ROOT_NODE) +@JsonIgnoreProperties(ignoreUnknown = true) +@JsonDeserialize(using = UsersDeserializer.class) +@JsonSerialize(using = UsersSerializer.class) +public class Users extends Page { + public static final String URI = "/gdc/projects/{workspaceId}/users"; + + static final String ROOT_NODE = "users"; + + Users(final List items, final Paging paging) { + super(items, paging); + } + + public Users(final User... users) { + this(asList(users), null); + } +} diff --git a/gooddata-java-model/src/main/java/com/gooddata/sdk/model/workspace/UsersDeserializer.java b/gooddata-java-model/src/main/java/com/gooddata/sdk/model/workspace/UsersDeserializer.java new file mode 100644 index 000000000..d9c427c55 --- /dev/null +++ b/gooddata-java-model/src/main/java/com/gooddata/sdk/model/workspace/UsersDeserializer.java @@ -0,0 +1,24 @@ +/* + * Copyright (C) 2004-2021, GoodData(R) Corporation. All rights reserved. + * This source code is licensed under the BSD-style license found in the + * LICENSE.txt file in the root directory of this source tree. + */ +package com.gooddata.sdk.model.workspace; + +import com.gooddata.sdk.common.collections.PageDeserializer; +import com.gooddata.sdk.common.collections.Paging; + +import java.util.List; +import java.util.Map; + +class UsersDeserializer extends PageDeserializer { + + protected UsersDeserializer() { + super(User.class, Users.ROOT_NODE); + } + + @Override + protected Users createPage(final List items, final Paging paging, final Map links) { + return new Users(items, paging); + } +} diff --git a/gooddata-java-model/src/main/java/com/gooddata/sdk/model/workspace/UsersSerializer.java b/gooddata-java-model/src/main/java/com/gooddata/sdk/model/workspace/UsersSerializer.java new file mode 100644 index 000000000..01efc4f06 --- /dev/null +++ b/gooddata-java-model/src/main/java/com/gooddata/sdk/model/workspace/UsersSerializer.java @@ -0,0 +1,35 @@ +/* + * Copyright (C) 2004-2021, GoodData(R) Corporation. All rights reserved. + * This source code is licensed under the BSD-style license found in the + * LICENSE.txt file in the root directory of this source tree. + */ +package com.gooddata.sdk.model.workspace; + + +import com.fasterxml.jackson.core.JsonGenerator; +import com.fasterxml.jackson.core.ObjectCodec; +import com.fasterxml.jackson.databind.JsonSerializer; +import com.fasterxml.jackson.databind.SerializerProvider; + +import java.io.IOException; + +/** + * Serializer of {@link Users} + */ +class UsersSerializer extends JsonSerializer { + + @Override + public void serialize(final Users users, final JsonGenerator jgen, final SerializerProvider serializerProvider) throws IOException { + jgen.writeStartObject(); + jgen.writeFieldName(Users.ROOT_NODE); + + jgen.writeStartArray(); + final ObjectCodec codec = jgen.getCodec(); + for (Object item: users.getPageItems()) { + codec.writeValue(jgen, item); + } + jgen.writeEndArray(); + + jgen.writeEndObject(); + } +} diff --git a/gooddata-java-model/src/main/java/com/gooddata/sdk/model/workspace/Workspace.java b/gooddata-java-model/src/main/java/com/gooddata/sdk/model/workspace/Workspace.java new file mode 100644 index 000000000..9bedb5f28 --- /dev/null +++ b/gooddata-java-model/src/main/java/com/gooddata/sdk/model/workspace/Workspace.java @@ -0,0 +1,528 @@ +/* + * Copyright (C) 2004-2021, GoodData(R) Corporation. All rights reserved. + * This source code is licensed under the BSD-style license found in the + * LICENSE.txt file in the root directory of this source tree. + */ +package com.gooddata.sdk.model.workspace; + +import com.fasterxml.jackson.annotation.*; +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; +import com.gooddata.sdk.common.util.BooleanDeserializer; +import com.gooddata.sdk.common.util.GDZonedDateTimeDeserializer; +import com.gooddata.sdk.common.util.GoodDataToStringBuilder; +import com.gooddata.sdk.model.md.Meta; +import com.gooddata.sdk.model.workspace.Environment; +import com.gooddata.sdk.model.workspace.WorkspaceDriver; +import com.gooddata.sdk.model.workspace.WorkspaceEnvironment; +import com.gooddata.sdk.model.workspace.Workspaces; +import com.gooddata.sdk.model.util.UriHelper; + +import java.time.ZonedDateTime; +import java.util.HashSet; +import java.util.Set; + +import static com.gooddata.sdk.common.util.Validate.*; +import static java.util.Arrays.asList; + +/** + * Workspace in GoodData platform + */ +@JsonTypeName("project") +@JsonTypeInfo(include = JsonTypeInfo.As.WRAPPER_OBJECT, use = JsonTypeInfo.Id.NAME) +@JsonIgnoreProperties(ignoreUnknown = true) +@JsonInclude(JsonInclude.Include.NON_NULL) +public class Workspace { + + public static final String URI = Workspaces.URI + "/{id}"; + private static final Set PREPARING_STATES = new HashSet<>(asList("PREPARING", "PREPARED", "LOADING")); + + @JsonProperty("content") + private final WorkspaceContent content; + + @JsonProperty("meta") + private final WorkspaceMeta meta; + + @JsonIgnore + private Links links; + + public Workspace(String title, String authorizationToken) { + content = new WorkspaceContent(authorizationToken); + meta = new WorkspaceMeta(title); + } + + public Workspace(String title, String summary, String authorizationToken) { + content = new WorkspaceContent(authorizationToken); + meta = new WorkspaceMeta(title, summary); + } + + @JsonCreator + private Workspace(@JsonProperty("content") WorkspaceContent content, @JsonProperty("meta") WorkspaceMeta meta, + @JsonProperty("links") Links links) { + this.content = content; + this.meta = meta; + this.links = links; + } + + public void setWorkspaceTemplate(String uri) { + meta.setWorkspaceTemplate(uri); + } + + @JsonIgnore + public String getId() { + return UriHelper.getLastUriPart(getUri()); + } + + @JsonIgnore + public String getState() { + return content.getState(); + } + + @JsonIgnore + public String getAuthorizationToken() { + return content.getAuthorizationToken(); + } + + @JsonIgnore + public String getDriver() { + return content.getDriver(); + } + + @JsonIgnore + public String getGuidedNavigation() { + return content.getGuidedNavigation(); + } + + @JsonIgnore + public String getCluster() { + return content.getCluster(); + } + + @JsonIgnore + public Boolean isPublic() { + return "1".equals(content.getIsPublic()); + } + + @JsonIgnore + public String getTitle() { + return meta.getTitle(); + } + + @JsonIgnore + public String getSummary() { + return meta.getSummary(); + } + + @JsonIgnore + public String getAuthor() { + return meta.getAuthor(); + } + + @JsonIgnore + public String getContributor() { + return meta.getContributor(); + } + + @JsonIgnore + public ZonedDateTime getCreated() { + return meta.getCreated(); + } + + @JsonIgnore + public ZonedDateTime getUpdated() { + return meta.getUpdated(); + } + + @JsonIgnore + public String getUri() { + return notNullState(links, "links").getSelf(); + } + + @JsonIgnore + public String getUsersUri() { + return notNullState(links, "links").getUsers(); + } + + @JsonIgnore + public String getRolesUri() { + return notNullState(links, "links").getRoles(); + } + + @JsonIgnore + public String getGroupsUri() { + return notNullState(links, "links").getGroups(); + } + + @JsonIgnore + public String getInvitationsUri() { + return notNullState(links, "links").getInvitations(); + } + + @JsonIgnore + public String getLdmUri() { + return links.getLdm(); + } + + @JsonIgnore + public String getLdmThumbnailUri() { + return notNullState(links, "links").getLdmThumbnail(); + } + + @JsonIgnore + public String getMetadataUri() { + return notNullState(links, "links").getMetadata(); + } + + @JsonIgnore + public String getPublicArtifactsUri() { + return notNullState(links, "links").getPublicArtifacts(); + } + + @JsonIgnore + public String getTemplatesUri() { + return notNullState(links, "links").getTemplates(); + } + + @JsonIgnore + public String getConnectorsUri() { + return notNullState(links, "links").getConnectors(); + } + + @JsonIgnore + public String getDataLoadUri() { + return notNullState(links, "links").getDataLoad(); + } + + @JsonIgnore + public String getSchedulesUri() { + return notNullState(links, "links").getSchedules(); + } + + @JsonIgnore + public String getExecuteUri() { + return notNullState(links, "links").getExecute(); + } + + @JsonIgnore + public String getEventStoresUri() { + return notNullState(links, "links").getEventStores(); + } + + @JsonIgnore + public String getClearCachesUri() { + return notNullState(links, "links").getClearCaches(); + } + + @JsonIgnore + public String getUploadsUri() { + return notNullState(links, "links").getUploads(); + } + + @JsonIgnore + public boolean isPreparing() { + return PREPARING_STATES.contains(getState()); + } + + @JsonIgnore + public boolean isEnabled() { + return "ENABLED".equals(getState()); + } + + public void setDriver(String driver) { + notEmpty(driver, "driver"); + content.setDriver(driver); + } + + public void setDriver(WorkspaceDriver driver) { + notNull(driver, "driver"); + setDriver(driver.getValue()); + } + + @JsonIgnore + public String getEnvironment() { + return content.getEnvironment(); + } + + @JsonIgnore + public void setEnvironment(final String environment) { + content.setEnvironment(environment); + } + + public void setEnvironment(final WorkspaceEnvironment environment) { + notNull(environment, "environment"); + setEnvironment(environment.name()); + } + + public void setEnvironment(final Environment environment) { + notNull(environment, "environment"); + setEnvironment(environment.name()); + } + + @Override + public String toString() { + return GoodDataToStringBuilder.defaultToString(this); + } + + @JsonIgnoreProperties(ignoreUnknown = true) + @JsonInclude(JsonInclude.Include.NON_NULL) + private static class WorkspaceContent { + + @JsonProperty("authorizationToken") + private final String authorizationToken; + + @JsonProperty("driver") + private String driver; + + @JsonProperty("guidedNavigation") + private final String guidedNavigation; + + @JsonIgnore + private String cluster; + + @JsonIgnore + private String isPublic; + + @JsonIgnore + private String state; + + @JsonProperty + private String environment; + + public WorkspaceContent(final String authorizationToken) { + this.authorizationToken = authorizationToken; + guidedNavigation = "1"; + driver = WorkspaceDriver.POSTGRES.getValue(); + } + + @JsonCreator + public WorkspaceContent(@JsonProperty("authorizationToken") String authorizationToken, + @JsonProperty("driver") String driver, + @JsonProperty("cluster") String cluster, + @JsonProperty("guidedNavigation") String guidedNavigation, + @JsonProperty("isPublic") String isPublic, + @JsonProperty("environment") String environment, + @JsonProperty("state") String state) { + this.authorizationToken = authorizationToken; + this.guidedNavigation = guidedNavigation; + this.driver = driver; + this.cluster = cluster; + this.isPublic = isPublic; + this.state = state; + this.environment = environment; + } + + public String getState() { + return state; + } + + public String getAuthorizationToken() { + return authorizationToken; + } + + public String getDriver() { + return driver; + } + + public String getGuidedNavigation() { + return guidedNavigation; + } + + public String getCluster() { + return cluster; + } + + public String getIsPublic() { + return isPublic; + } + + public void setDriver(String driver) { + this.driver = driver; + } + + public String getEnvironment() { + return environment; + } + + public void setEnvironment(final String environment) { + this.environment = environment; + } + + @Override + public String toString() { + return GoodDataToStringBuilder.defaultToString(this); + } + } + + @JsonIgnoreProperties(ignoreUnknown = true) + private static class Links { + private final String self; + private final String users; + private final String roles; + private final String groups; + private final String invitations; + private final String ldm; + private final String ldmThumbnail; + private final String metadata; + private final String publicArtifacts; + private final String templates; + private final String connectors; + private final String dataLoad; + private final String schedules; + private final String execute; + private final String eventStores; + private final String clearCaches; + private final String uploads; + + @JsonCreator + public Links(@JsonProperty("self") String self, + @JsonProperty("users") String users, + @JsonProperty("roles") String roles, + @JsonProperty("groups") String groups, + @JsonProperty("invitations") String invitations, + @JsonProperty("ldm") String ldm, + @JsonProperty("ldm_thumbnail") String ldmThumbnail, + @JsonProperty("metadata") String metadata, + @JsonProperty("publicartifacts") String publicArtifacts, + @JsonProperty("templates") String templates, + @JsonProperty("connectors") String connectors, + @JsonProperty("dataload") String dataLoad, + @JsonProperty("schedules") String schedules, + @JsonProperty("execute") String execute, + @JsonProperty("eventstores") String eventStores, + @JsonProperty("clearCaches") String clearCaches, + @JsonProperty("uploads") String uploads) { + this.self = self; + this.users = users; + this.roles = roles; + this.groups = groups; + this.invitations = invitations; + this.ldm = ldm; + this.ldmThumbnail = ldmThumbnail; + this.metadata = metadata; + this.publicArtifacts = publicArtifacts; + this.templates = templates; + this.connectors = connectors; + this.dataLoad = dataLoad; + this.schedules = schedules; + this.execute = execute; + this.eventStores = eventStores; + this.clearCaches = clearCaches; + this.uploads = uploads; + } + + public String getSelf() { + return self; + } + + public String getUsers() { + return users; + } + + public String getRoles() { + return roles; + } + + public String getGroups() { + return groups; + } + + public String getInvitations() { + return invitations; + } + + public String getLdm() { + return ldm; + } + + public String getLdmThumbnail() { + return ldmThumbnail; + } + + public String getMetadata() { + return metadata; + } + + public String getPublicArtifacts() { + return publicArtifacts; + } + + public String getTemplates() { + return templates; + } + + public String getConnectors() { + return connectors; + } + + public String getDataLoad() { + return dataLoad; + } + + public String getSchedules() { + return schedules; + } + + public String getExecute() { + return execute; + } + + public String getEventStores() { + return eventStores; + } + + public String getClearCaches() { + return clearCaches; + } + + public String getUploads() { + return uploads; + } + } + + private static class WorkspaceMeta extends Meta { + + private String workspaceTemplate; + + @JsonCreator + private WorkspaceMeta(@JsonProperty("author") String author, + @JsonProperty("contributor") String contributor, + @JsonProperty("created") @JsonDeserialize(using = GDZonedDateTimeDeserializer.class) ZonedDateTime created, + @JsonProperty("updated") @JsonDeserialize(using = GDZonedDateTimeDeserializer.class) ZonedDateTime updated, + @JsonProperty("summary") String summary, + @JsonProperty("title") String title, + @JsonProperty("category") String category, + @JsonProperty("tags") Set tags, + @JsonProperty("uri") String uri, + @JsonProperty("identifier") String identifier, + @JsonProperty("deprecated") @JsonDeserialize(using = BooleanDeserializer.class) Boolean deprecated, + @JsonProperty("isProduction") @JsonDeserialize(using = BooleanDeserializer.class) Boolean production, + @JsonProperty("locked") @JsonDeserialize(using = BooleanDeserializer.class) Boolean locked, + @JsonProperty("unlisted") @JsonDeserialize(using = BooleanDeserializer.class) Boolean unlisted, + @JsonProperty("sharedWithSomeone") @JsonDeserialize(using = BooleanDeserializer.class) Boolean sharedWithSomeone, + @JsonProperty("flags") Set flags) { + super(author, contributor, created, updated, summary, title, category, tags, uri, identifier, + deprecated, production, locked, unlisted, sharedWithSomeone, flags); + + } + + private WorkspaceMeta(String title) { + super(title); + } + + private WorkspaceMeta(String title, String summary) { + super(title, summary); + } + + public String getWorkspaceTemplate() { + return workspaceTemplate; + } + + public void setWorkspaceTemplate(String workspaceTemplate) { + this.workspaceTemplate = workspaceTemplate; + } + + @Override + public String toString() { + return GoodDataToStringBuilder.defaultToString(this); + } + } +} diff --git a/gooddata-java-model/src/main/java/com/gooddata/sdk/model/workspace/Workspaces.java b/gooddata-java-model/src/main/java/com/gooddata/sdk/model/workspace/Workspaces.java new file mode 100644 index 000000000..dca8b2f0a --- /dev/null +++ b/gooddata-java-model/src/main/java/com/gooddata/sdk/model/workspace/Workspaces.java @@ -0,0 +1,60 @@ +/* + * Copyright (C) 2004-2021, GoodData(R) Corporation. All rights reserved. + * This source code is licensed under the BSD-style license found in the + * LICENSE.txt file in the root directory of this source tree. + */ +package com.gooddata.sdk.model.workspace; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonTypeInfo; +import com.fasterxml.jackson.annotation.JsonTypeName; +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; +import com.gooddata.sdk.common.collections.Page; +import com.gooddata.sdk.common.collections.PageDeserializer; +import com.gooddata.sdk.common.collections.Paging; +import com.gooddata.sdk.common.util.GoodDataToStringBuilder; + +import java.util.Collection; +import java.util.List; +import java.util.Map; + +import static com.gooddata.sdk.model.workspace.Workspaces.ROOT_NODE; + +/** + * Collection of GoodData workspaces being returned from API. + */ +@JsonDeserialize(using = Workspaces.Deserializer.class) +@JsonTypeInfo(include = JsonTypeInfo.As.WRAPPER_OBJECT, use = JsonTypeInfo.Id.NAME) +@JsonTypeName(ROOT_NODE) +@JsonIgnoreProperties(ignoreUnknown = true) +@JsonInclude(JsonInclude.Include.NON_NULL) +public class Workspaces extends Page { + + public static final String URI = "/gdc/projects"; + public static final String LIST_WORKSPACES_URI = "/gdc/account/profile/{id}/projects"; + + static final String ROOT_NODE = "projects"; + + static class Deserializer extends PageDeserializer { + + protected Deserializer() { + super(Workspace.class); + } + + @Override + protected Workspaces createPage(final List items, final Paging paging, final Map links) { + return new Workspaces(items, paging); + } + } + + public Workspaces(List items, Paging paging) { + super(items, paging); + } + + @Override + public String toString() { + return GoodDataToStringBuilder.defaultToString(this); + } + +} diff --git a/gooddata-java/src/main/java/com/gooddata/sdk/service/workspace/RoleNotFoundException.java b/gooddata-java/src/main/java/com/gooddata/sdk/service/workspace/RoleNotFoundException.java new file mode 100644 index 000000000..205cca062 --- /dev/null +++ b/gooddata-java/src/main/java/com/gooddata/sdk/service/workspace/RoleNotFoundException.java @@ -0,0 +1,25 @@ +/* + * Copyright (C) 2004-2019, GoodData(R) Corporation. All rights reserved. + * This source code is licensed under the BSD-style license found in the + * LICENSE.txt file in the root directory of this source tree. + */ +package com.gooddata.sdk.service.project; + +import com.gooddata.sdk.common.GoodDataException; + +/** + * Project role of the given URI doesn't exist + */ +public class RoleNotFoundException extends GoodDataException { + + private final String uri; + + public RoleNotFoundException(String uri, Throwable cause) { + super("Role " + uri + " was not found", cause); + this.uri = uri; + } + + public String getUri() { + return uri; + } +} diff --git a/gooddata-java/src/main/java/com/gooddata/sdk/service/workspace/UserInWorkspaceNotFoundException.java b/gooddata-java/src/main/java/com/gooddata/sdk/service/workspace/UserInWorkspaceNotFoundException.java new file mode 100644 index 000000000..52749453a --- /dev/null +++ b/gooddata-java/src/main/java/com/gooddata/sdk/service/workspace/UserInWorkspaceNotFoundException.java @@ -0,0 +1,22 @@ +/* + * Copyright (C) 2004-2019, GoodData(R) Corporation. All rights reserved. + * This source code is licensed under the BSD-style license found in the + * LICENSE.txt file in the root directory of this source tree. + */ +package com.gooddata.sdk.service.project; + +import com.gooddata.sdk.common.GoodDataException; + +/** + * User in project is not found + */ +public class UserInProjectNotFoundException extends GoodDataException { + + public UserInProjectNotFoundException(final String message) { + super(message); + } + + public UserInProjectNotFoundException(final String message, final Throwable cause) { + super(message, cause); + } +} diff --git a/gooddata-java/src/main/java/com/gooddata/sdk/service/workspace/WorkspaceNotFoundException.java b/gooddata-java/src/main/java/com/gooddata/sdk/service/workspace/WorkspaceNotFoundException.java new file mode 100644 index 000000000..06851e520 --- /dev/null +++ b/gooddata-java/src/main/java/com/gooddata/sdk/service/workspace/WorkspaceNotFoundException.java @@ -0,0 +1,25 @@ +/* + * Copyright (C) 2004-2019, GoodData(R) Corporation. All rights reserved. + * This source code is licensed under the BSD-style license found in the + * LICENSE.txt file in the root directory of this source tree. + */ +package com.gooddata.sdk.service.project; + +import com.gooddata.sdk.common.GoodDataException; + +/** + * Project of the given URI doesn't exist + */ +public class ProjectNotFoundException extends GoodDataException { + + private final String projectUri; + + public ProjectNotFoundException(String projectUri, Throwable cause) { + super("Project " + projectUri + " was not found", cause); + this.projectUri = projectUri; + } + + public String getProjectUri() { + return projectUri; + } +} diff --git a/gooddata-java/src/main/java/com/gooddata/sdk/service/workspace/WorkspaceService.java b/gooddata-java/src/main/java/com/gooddata/sdk/service/workspace/WorkspaceService.java new file mode 100644 index 000000000..84e9c8722 --- /dev/null +++ b/gooddata-java/src/main/java/com/gooddata/sdk/service/workspace/WorkspaceService.java @@ -0,0 +1,577 @@ +/* + * Copyright (C) 2004-2021, GoodData(R) Corporation. All rights reserved. + * This source code is licensed under the BSD-style license found in the + * LICENSE.txt file in the root directory of this source tree. + */ +package com.gooddata.sdk.service.workspace; + +import com.gooddata.sdk.common.GoodDataException; +import com.gooddata.sdk.common.GoodDataRestException; +import com.gooddata.sdk.common.collections.CustomPageRequest; +import com.gooddata.sdk.common.collections.Page; +import com.gooddata.sdk.common.collections.PageBrowser; +import com.gooddata.sdk.common.collections.PageRequest; +import com.gooddata.sdk.common.util.SpringMutableUri; +import com.gooddata.sdk.model.account.Account; +import com.gooddata.sdk.model.gdc.AsyncTask; +import com.gooddata.sdk.model.gdc.UriResponse; +import com.gooddata.sdk.model.workspace.*; +import com.gooddata.sdk.service.*; +import com.gooddata.sdk.service.account.AccountService; +import com.gooddata.sdk.service.workspace.WorkspaceNotFoundException; +import com.gooddata.sdk.service.workspace.WorkspaceUsersUpdateException; +import com.gooddata.sdk.service.workspace.RoleNotFoundException; +import com.gooddata.sdk.service.workspace.UserInWorkspaceNotFoundException; +import org.springframework.http.HttpStatus; +import org.springframework.http.client.ClientHttpResponse; +import org.springframework.web.client.RestClientException; +import org.springframework.web.client.RestTemplate; +import org.springframework.web.util.UriTemplate; + +import java.io.IOException; +import java.net.URI; +import java.util.*; +import java.util.stream.Collectors; + +import static com.gooddata.sdk.common.util.Validate.*; +import static java.util.Arrays.asList; + +/** + * Manage workspaces (create, list, ...) + *

+ * Usage example: + *


+ *     WorkspaceService workspaceService = gd.getWorkspaceService();
+ *     Iterable<Workspace> workspaces = workspaceService.listWorkspaces().getAllItems();
+ *     Workspace workspace = workspaceService.createWorkspace(new Workspace("My Workspace", "MyToken"));
+ * 
+ */ +public class WorkspaceService extends AbstractService { + + public static final UriTemplate WORKSPACE_TEMPLATE = new UriTemplate(Workspace.URI); + public static final UriTemplate WORKSPACE_USERS_TEMPLATE = new UriTemplate(Users.URI); + public static final UriTemplate WORKSPACE_USER_TEMPLATE = new UriTemplate(User.URI); + public static final UriTemplate LIST_WORKSPACES_TEMPLATE = new UriTemplate(Workspaces.LIST_WORKSPACES_URI); + private final AccountService accountService; + + /** + * Constructs service for GoodData workspace management (list workspaces, create a workspace, ...). + * @param restTemplate RESTful HTTP Spring template + * @param accountService GoodData account service + * @param settings settings + */ + public WorkspaceService(final RestTemplate restTemplate, final AccountService accountService, + final GoodDataSettings settings) { + super(restTemplate, settings); + this.accountService = notNull(accountService, "accountService"); + } + + /** + * Get browser of workspaces that current user has access to. + * + * @return {@link PageBrowser} list of found workspaces or empty list + */ + public PageBrowser listWorkspaces() { + final String userId = accountService.getCurrent().getId(); + return new PageBrowser<>(page -> listWorkspaces(getWorkspacesUri(userId, page))); + } + + /** + * Get defined page of paged list of workspaces that current user has access to. + * + * @param startPage page to be retrieved first + * @return {@link PageBrowser} list of found workspaces or empty list + */ + public PageBrowser listWorkspaces(final PageRequest startPage) { + notNull(startPage, "startPage"); + final String userId = accountService.getCurrent().getId(); + return new PageBrowser<>(startPage, page -> listWorkspaces(getWorkspacesUri(userId, page))); + } + + /** + * Get defined page of paged list of workspaces that given user/account has access to. + * + * @param account user whose workspaces will be returned + * @param startPage page to be retrieved + * @return {@link PageBrowser} list of found workspaces for given user or empty list + */ + public PageBrowser listWorkspaces(final Account account, final PageRequest startPage) { + notNull(startPage, "startPage"); + notNull(account, "account"); + notEmpty(account.getId(), "account.uri"); + return new PageBrowser<>(startPage, page -> listWorkspaces(getWorkspacesUri(account.getId(), page))); + } + + /** + * Get browser of workspaces that given user/account has access to. + * + * @param account user whose workspaces will be returned + * @return {@link PageBrowser} list of found workspaces for given user or empty list + */ + public PageBrowser listWorkspaces(final Account account) { + return listWorkspaces(account, new CustomPageRequest()); + } + + private Page listWorkspaces(final URI uri) { + try { + final Workspaces workspaces = restTemplate.getForObject(uri, Workspaces.class); + if (workspaces == null) { + return new Page<>(); + } + return workspaces; + } catch (GoodDataException | RestClientException e) { + throw new GoodDataException("Unable to list workspaces", e); + } + } + + private static URI getWorkspacesUri(final String userId) { + notEmpty(userId, "userId"); + return LIST_WORKSPACES_TEMPLATE.expand(userId); + } + + private static URI getWorkspacesUri(final String userId, final PageRequest page) { + return page.getPageUri(new SpringMutableUri(getWorkspacesUri(userId))); + } + + /** + * Create new workspace. + * + * @param workspace workspace to be created + * @return created workspace (including very useful id) + * @throws GoodDataException when workspaces creation fails + */ + public FutureResult createWorkspace(final Workspace workspace) { + notNull(workspace, "workspace"); + + final UriResponse uri; + try { + uri = restTemplate.postForObject(Workspaces.URI, workspace, UriResponse.class); + } catch (GoodDataException | RestClientException e) { + throw new GoodDataException("Unable to create workspace", e); + } + + if (uri == null) { + throw new GoodDataException("Empty response when workspace POSTed to API"); + } + + return new PollResult<>(this, new SimplePollHandler(uri.getUri(), Workspace.class) { + + @Override + public boolean isFinished(ClientHttpResponse response) throws IOException { + final Workspace workspace = extractData(response, Workspace.class); + return !workspace.isPreparing(); + } + + @Override + protected void onFinish() { + if (!getResult().isEnabled()) { + throw new GoodDataException("Created workspace " + uri + " is not enabled"); + } + } + + @Override + public void handlePollException(final GoodDataRestException e) { + throw new GoodDataException("Creating workspace " + uri + " failed", e); + } + }); + } + + /** + * Get workspace by URI. + * + * @param uri URI of workspace resource (/gdc/workspaces/{id}) + * @return workspace + * @throws GoodDataException when workspace can't be accessed + */ + public Workspace getWorkspaceByUri(final String uri) { + notEmpty(uri, "uri"); + try { + return restTemplate.getForObject(uri, Workspace.class); + } catch (GoodDataRestException e) { + if (HttpStatus.NOT_FOUND.value() == e.getStatusCode()) { + throw new WorkspaceNotFoundException(uri, e); + } else { + throw e; + } + } catch (RestClientException e) { + throw new GoodDataException("Unable to get workspace " + uri, e); + } + } + + /** + * Get workspace by id. + * + * @param id id of workspace + * @return workspace + * @throws GoodDataException when workspace can't be accessed + */ + public Workspace getWorkspaceById(String id) { + notEmpty(id, "id"); + return getWorkspaceByUri(WORKSPACE_TEMPLATE.expand(id).toString()); + } + + /** + * Removes given workspace + * @param workspace workspace to be removed + * @throws GoodDataException when workspace can't be deleted + */ + public void removeWorkspace(final Workspace workspace) { + notNull(workspace, "workspace"); + notNull(workspace.getUri(), "workspace.uri"); + + try { + restTemplate.delete(workspace.getUri()); + } catch (GoodDataRestException | RestClientException e) { + throw new GoodDataException("Unable to delete workspace " + workspace.getUri(), e); + } + } + + public Collection getWorkspaceTemplates(final Workspace workspace) { + notNull(workspace, "workspace"); + notNull(workspace.getId(), "workspace.id"); + + try { + final WorkspaceTemplates templates = restTemplate.getForObject(WorkspaceTemplate.URI, WorkspaceTemplates.class, workspace.getId()); + return templates != null && templates.getTemplatesInfo() != null ? templates.getTemplatesInfo() : Collections.emptyList(); + } catch (GoodDataRestException | RestClientException e) { + throw new GoodDataException("Unable to get workspace templates", e); + } + } + + /** + * Get available validation types for workspace. Which can be passed to {@link #validateWorkspace(Workspace, WorkspaceValidationType...)}. + * + * @param workspace workspace to fetch validation types for + * @return available validations + */ + public Set getAvailableWorkspaceValidationTypes(final Workspace workspace) { + notNull(workspace, "workspace"); + notNull(workspace.getId(), "workspace.id"); + + try { + final WorkspaceValidations workspaceValidations = restTemplate.getForObject(WorkspaceValidations.URI, WorkspaceValidations.class, workspace.getId()); + return workspaceValidations != null ? workspaceValidations.getValidations() : Collections.emptySet(); + } catch (GoodDataRestException | RestClientException e) { + throw new GoodDataException("Unable to get workspace available validation types", e); + } + } + + /** + * Validate workspace using all available validations. + * + * @param workspace workspace to validate + * @return results of validation + */ + public FutureResult validateWorkspace(final Workspace workspace) { + return validateWorkspace(workspace, getAvailableWorkspaceValidationTypes(workspace)); + } + + /** + * Validate workspace with given validations + * + * @param workspace workspace to validate + * @param validations validations to use + * @return results of validation + */ + public FutureResult validateWorkspace(final Workspace workspace, WorkspaceValidationType... validations) { + return validateWorkspace(workspace, new HashSet<>(asList(validations))); + } + + /** + * Validate workspace with given validations + * + * @param workspace workspace to validate + * @param validations validations to use + * @return results of validation + */ + public FutureResult validateWorkspace(final Workspace workspace, Set validations) { + notNull(workspace, "workspace"); + notNull(workspace.getId(), "workspace.id"); + + final AsyncTask task; + try { + task = restTemplate.postForObject(WorkspaceValidations.URI, new WorkspaceValidations(validations), AsyncTask.class, workspace.getId()); + } catch (GoodDataException | RestClientException e) { + throw new GoodDataException("Unable to to start workspace validation", e); + } + return new PollResult<>(this, + // PollHandler able to poll on different URIs (by the Location header) + // poll class is Void because the object returned varies between invocations (even on the same URI) + new AbstractPollHandler(notNullState(task, "workspace validation task").getUri(), + Void.class, WorkspaceValidationResults.class) { + + @Override + public boolean isFinished(ClientHttpResponse response) throws IOException { + final URI location = response.getHeaders().getLocation(); + if (location != null) { + setPollingUri(location.toString()); + } + final boolean finished = super.isFinished(response); + if (finished) { + try { + final WorkspaceValidationResults result = restTemplate.getForObject(getPollingUri(), getResultClass()); + setResult(result); + } catch (GoodDataException | RestClientException e) { + throw new GoodDataException("Unable to obtain validation results from " + getPollingUri()); + } + } + return finished; + } + + @Override + public void handlePollResult(final Void pollResult) { + } + + @Override + public void handlePollException(final GoodDataRestException e) { + throw new GoodDataException("Workspace validation failed: " + getPollingUri(), e); + } + }); + } + + /** + * Get browser of users by given workspace. + * + * @param workspace workspace of users + * @return {@link PageBrowser} list of found users or empty list + */ + public PageBrowser listUsers(final Workspace workspace) { + notNull(workspace, "workspace"); + return new PageBrowser<>(page -> listUsers(getUsersUri(workspace, page))); + } + + /** + * Get defined page of paged list of users by given workspace. + * + * @param workspace workspace of users + * @param startPage page to be retrieved first + * @return {@link PageBrowser} list of found users or empty list + */ + public PageBrowser listUsers(final Workspace workspace, final PageRequest startPage) { + notNull(workspace, "workspace"); + notNull(startPage, "startPage"); + return new PageBrowser<>(startPage, page -> listUsers(getUsersUri(workspace, page))); + } + + private Page listUsers(final URI uri) { + try { + final Users users = restTemplate.getForObject(uri, Users.class); + if (users == null) { + return new Page<>(); + } + return users; + } catch (GoodDataException | RestClientException e) { + throw new GoodDataException("Unable to list users", e); + } + } + + private static URI getUsersUri(final Workspace workspace) { + notNull(workspace.getId(), "workspace.id"); + return WORKSPACE_USERS_TEMPLATE.expand(workspace.getId()); + } + + private static URI getUsersUri(final Workspace workspace, final PageRequest page) { + return page.getPageUri(new SpringMutableUri(getUsersUri(workspace))); + } + + private static URI getUserUri(final Workspace workspace, final Account account) { + notNull(account.getId(), "account.id"); + return WORKSPACE_USER_TEMPLATE.expand(workspace.getId(), account.getId()); + } + + /** + * Get set of user role by given workspace. + * + * Note: This makes n+1 API calls to retrieve all role details. + * + * @param workspace workspace of roles + * @return set of found roles or empty set + */ + public Set getRoles(final Workspace workspace) { + notNull(workspace, "workspace"); + notNull(workspace.getId(), "workspace.id"); + + final Roles roles = restTemplate.getForObject(Roles.URI, Roles.class, workspace.getId()); + if (roles == null) { + return Collections.emptySet(); + } else { + final Set result = new HashSet<>(); + for (String roleUri : roles.getRoles()) { + final Role role = restTemplate.getForObject(roleUri, Role.class); + notNullState(role, "role").setUri(roleUri); + result.add(role); + } + return result; + } + } + + /** + * Get role by given URI. + * + * @param uri role uri + * @return found role + * @throws RoleNotFoundException when the role doesn't exist + */ + public Role getRoleByUri(String uri) { + notEmpty(uri, "uri"); + try { + final Role role = restTemplate.getForObject(uri, Role.class); + notNullState(role, "role").setUri(uri); + return role; + } catch (GoodDataRestException e) { + if (HttpStatus.NOT_FOUND.value() == e.getStatusCode()) { + throw new RoleNotFoundException(uri, e); + } else { + throw e; + } + } catch (RestClientException e) { + throw new GoodDataException("Unable to get role " + uri, e); + } + } + + /** + * Send workspace invitations to users + * @param workspace target workspace + * @param invitations invitations + * @return created invitation + */ + public CreatedInvitations sendInvitations(final Workspace workspace, final Invitation... invitations) { + notNull(workspace, "workspace"); + notNull(workspace.getId(), "workspace.id"); + noNullElements(invitations, "invitations"); + + try { + return restTemplate.postForObject(Invitations.URI, new Invitations(invitations), CreatedInvitations.class, workspace.getId()); + } catch (RestClientException e) { + final String emails = Arrays.stream(invitations).map(Invitation::getEmail).collect(Collectors.joining(",")); + throw new GoodDataException("Unable to invite " + emails + " to workspace " + workspace.getId(), e); + } + } + + /** + * get user in workspace + * + * @param workspace where to find + * @param account which user to find + * @return User representation in workspace + * @throws UserInWorkspaceNotFoundException when user is not in workspace + */ + public User getUser(final Workspace workspace, final Account account) { + notNull(account, "account"); + notEmpty(account.getId(), "account.id"); + notNull(workspace, "workspace"); + + try { + return restTemplate.getForObject(getUserUri(workspace, account), User.class); + } catch (GoodDataRestException e) { + if (HttpStatus.NOT_FOUND.value() == e.getStatusCode()) { + throw new UserInWorkspaceNotFoundException("User " + account.getId() + " is not in workspace", e); + } else { + throw e; + } + } catch (RestClientException e) { + throw new GoodDataException("Unable to get user " + account.getId() + " in workspace", e); + } + } + + /** Add user in to the workspace + * + * @param workspace where to add user + * @param account to be added + * @param userRoles list of user roles + * @return user added to the workspace + * @throws WorkspaceUsersUpdateException in case of failure + */ + public User addUserToWorkspace(final Workspace workspace, final Account account, final Role... userRoles) { + notNull(workspace, "workspace"); + notNull(account, "account"); + notEmpty(account.getUri(), "account.uri"); + notNull(userRoles, "userRoles"); + validateRoleURIs(userRoles); + notEmpty(workspace.getId(), "workspace.id"); + + final User user = new User(account, userRoles); + + doPostWorkspaceUsersUpdate(workspace, user); + + return getUser(workspace, account); + } + + /** + * Checks whether all the roles have URI specified. + */ + private void validateRoleURIs(final Role[] userRoles) { + final List invalidRoles = new ArrayList<>(); + for(Role role: userRoles) { + if(role.getUri() == null) { + invalidRoles.add(role.getIdentifier()); + } + } + if (!invalidRoles.isEmpty()) { + throw new IllegalArgumentException("Roles with URI not specified found: " + invalidRoles); + } + } + + /** + * Update user in the workspace + * + * @param workspace in which to update user + * @param users to update + * @throws WorkspaceUsersUpdateException in case of failure + */ + public void updateUserInWorkspace(final Workspace workspace, final User... users) { + notNull(workspace, "workspace"); + notNull(users, "users"); + notEmpty(workspace.getId(), "workspace.id"); + + doPostWorkspaceUsersUpdate(workspace, users); + } + + private void doPostWorkspaceUsersUpdate(final Workspace workspace, final User... users) { + final URI usersUri = getUsersUri(workspace); + + try { + final WorkspaceUsersUpdateResult workspaceUsersUpdateResult = restTemplate.postForObject(usersUri, new Users(users), WorkspaceUsersUpdateResult.class); + + if (! notNullState(workspaceUsersUpdateResult, "workspaceUsersUpdateResult").getFailed().isEmpty()) { + throw new WorkspaceUsersUpdateException("Unable to update users: " + workspaceUsersUpdateResult.getFailed()); + } + } catch (RestClientException e) { + throw new GoodDataException("Unable to update users in workspace", e); + } + } + + /** + * Removes given account from a workspace without checking if really account is in workspace. + *

+ * You can: + *

    + *
  • Remove yourself from a workspace (leave the workspace). You cannot leave the workspace if you are the only admin in the workspace.
  • + *
  • Remove another user from a workspace. You need to have the canSuspendUser permission in this workspace.
  • + *
+ *

+ * @param account account to be removed + * @param workspace workspace from user will be removed + * @throws GoodDataException when account can't be removed + */ + public void removeUserFromWorkspace(final Workspace workspace, final Account account) { + notNull(workspace, "workspace"); + notNull(workspace.getId(), "workspace.id"); + notNull(account, "account"); + notNull(account.getId(), "account.id"); + + try { + restTemplate.delete(getUserUri(workspace, account)); + } catch (GoodDataRestException e) { + if (HttpStatus.FORBIDDEN.value() == e.getStatusCode()) { + throw new GoodDataException("You cannot leave the workspace " + workspace.getId() + " if you are the only admin in it. You can make another user an admin in this workspace, and then re-issue the call.", e); + } else if (HttpStatus.METHOD_NOT_ALLOWED.value() == e.getStatusCode()) { + throw new GoodDataException("You either misspelled your user ID or tried to remove another user but did not have the canSuspendUser permission in this workspace. Check your ID in the request and your permissions in the workspace " + workspace.getId() + ", then re-issue the call.", e); + } else { + throw e; + } + } catch (RestClientException e) { + throw new GoodDataException("Unable to remove account " + account.getUri() + " from workspace " + workspace.getUri(), e); + } + } +} diff --git a/gooddata-java/src/main/java/com/gooddata/sdk/service/workspace/WorkspaceUsersUpdateException.java b/gooddata-java/src/main/java/com/gooddata/sdk/service/workspace/WorkspaceUsersUpdateException.java new file mode 100644 index 000000000..83f585c0c --- /dev/null +++ b/gooddata-java/src/main/java/com/gooddata/sdk/service/workspace/WorkspaceUsersUpdateException.java @@ -0,0 +1,22 @@ +/* + * Copyright (C) 2004-2019, GoodData(R) Corporation. All rights reserved. + * This source code is licensed under the BSD-style license found in the + * LICENSE.txt file in the root directory of this source tree. + */ +package com.gooddata.sdk.service.project; + +import com.gooddata.sdk.common.GoodDataException; + +/** + * Unable to update users in project + */ +public class ProjectUsersUpdateException extends GoodDataException { + + public ProjectUsersUpdateException(final String message, final Throwable cause) { + super(message, cause); + } + + public ProjectUsersUpdateException(final String message) { + super(message); + } +} From a982630efa1ca81f6f0c2c21149a8abf1fb19dc9 Mon Sep 17 00:00:00 2001 From: Libor Rysavy Date: Thu, 3 Mar 2022 10:05:35 +0100 Subject: [PATCH 2/2] test --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index ba44ceb88..0b9fee5a8 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,4 @@ +test # GoodData Java SDK [![Build Status](https://github.com/gooddata/gooddata-java/actions/workflows/build.yml/badge.svg?branch=master)](https://github.com/gooddata/gooddata-java/actions/workflows/build.yml) [![Coverage Status](https://codecov.io/gh/gooddata/gooddata-java/branch/master/graph/badge.svg)](https://app.codecov.io/gh/gooddata/gooddata-java/branch/master)