Skip to content

Commit

Permalink
add randomness to slug if dupe (#5210)
Browse files Browse the repository at this point in the history
  • Loading branch information
cgardens authored Aug 5, 2021
1 parent 40028dd commit 2fd311e
Show file tree
Hide file tree
Showing 3 changed files with 132 additions and 62 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -40,20 +40,21 @@
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.UUID;
import java.util.stream.Stream;

public class ConfigRepository {

private final ConfigPersistence persistence;

public ConfigRepository(ConfigPersistence persistence) {
public ConfigRepository(final ConfigPersistence persistence) {
this.persistence = persistence;
}

public StandardWorkspace getStandardWorkspace(final UUID workspaceId, boolean includeTombstone)
public StandardWorkspace getStandardWorkspace(final UUID workspaceId, final boolean includeTombstone)
throws JsonValidationException, IOException, ConfigNotFoundException {
StandardWorkspace workspace = persistence.getConfig(ConfigSchema.STANDARD_WORKSPACE, workspaceId.toString(), StandardWorkspace.class);
final StandardWorkspace workspace = persistence.getConfig(ConfigSchema.STANDARD_WORKSPACE, workspaceId.toString(), StandardWorkspace.class);

if (!MoreBooleans.isTruthy(workspace.getTombstone()) || includeTombstone) {
return workspace;
Expand All @@ -62,23 +63,26 @@ public StandardWorkspace getStandardWorkspace(final UUID workspaceId, boolean in
throw new ConfigNotFoundException(ConfigSchema.STANDARD_WORKSPACE, workspaceId.toString());
}

public StandardWorkspace getWorkspaceBySlug(String slug, boolean includeTombstone)
throws JsonValidationException, IOException, ConfigNotFoundException {
for (StandardWorkspace workspace : listStandardWorkspaces(includeTombstone)) {
public Optional<StandardWorkspace> getWorkspaceBySlugOptional(final String slug, final boolean includeTombstone)
throws JsonValidationException, IOException {
for (final StandardWorkspace workspace : listStandardWorkspaces(includeTombstone)) {
if (workspace.getSlug().equals(slug)) {
return workspace;
return Optional.of(workspace);
}
}
return Optional.empty();
}

throw new ConfigNotFoundException(ConfigSchema.STANDARD_WORKSPACE, slug);
public StandardWorkspace getWorkspaceBySlug(final String slug, final boolean includeTombstone)
throws JsonValidationException, IOException, ConfigNotFoundException {
return getWorkspaceBySlugOptional(slug, includeTombstone).orElseThrow(() -> new ConfigNotFoundException(ConfigSchema.STANDARD_WORKSPACE, slug));
}

public List<StandardWorkspace> listStandardWorkspaces(boolean includeTombstone)
throws JsonValidationException, IOException {
public List<StandardWorkspace> listStandardWorkspaces(final boolean includeTombstone) throws JsonValidationException, IOException {

List<StandardWorkspace> workspaces = new ArrayList<StandardWorkspace>();
final List<StandardWorkspace> workspaces = new ArrayList<>();

for (StandardWorkspace workspace : persistence.listConfigs(ConfigSchema.STANDARD_WORKSPACE, StandardWorkspace.class)) {
for (final StandardWorkspace workspace : persistence.listConfigs(ConfigSchema.STANDARD_WORKSPACE, StandardWorkspace.class)) {
if (!MoreBooleans.isTruthy(workspace.getTombstone()) || includeTombstone) {
workspaces.add(workspace);
}
Expand All @@ -96,20 +100,20 @@ public StandardSourceDefinition getStandardSourceDefinition(final UUID sourceDef
return persistence.getConfig(ConfigSchema.STANDARD_SOURCE_DEFINITION, sourceDefinitionId.toString(), StandardSourceDefinition.class);
}

public StandardSourceDefinition getSourceDefinitionFromSource(UUID sourceId) {
public StandardSourceDefinition getSourceDefinitionFromSource(final UUID sourceId) {
try {
final SourceConnection source = getSourceConnection(sourceId);
return getStandardSourceDefinition(source.getSourceDefinitionId());
} catch (Exception e) {
} catch (final Exception e) {
throw new RuntimeException(e);
}
}

public StandardSourceDefinition getSourceDefinitionFromConnection(UUID connectionId) {
public StandardSourceDefinition getSourceDefinitionFromConnection(final UUID connectionId) {
try {
final StandardSync sync = getStandardSync(connectionId);
return getSourceDefinitionFromSource(sync.getSourceId());
} catch (Exception e) {
} catch (final Exception e) {
throw new RuntimeException(e);
}
}
Expand All @@ -128,20 +132,20 @@ public StandardDestinationDefinition getStandardDestinationDefinition(final UUID
StandardDestinationDefinition.class);
}

public StandardDestinationDefinition getDestinationDefinitionFromDestination(UUID destinationId) {
public StandardDestinationDefinition getDestinationDefinitionFromDestination(final UUID destinationId) {
try {
final DestinationConnection destination = getDestinationConnection(destinationId);
return getStandardDestinationDefinition(destination.getDestinationDefinitionId());
} catch (Exception e) {
} catch (final Exception e) {
throw new RuntimeException(e);
}
}

public StandardDestinationDefinition getDestinationDefinitionFromConnection(UUID connectionId) {
public StandardDestinationDefinition getDestinationDefinitionFromConnection(final UUID connectionId) {
try {
final StandardSync sync = getStandardSync(connectionId);
return getDestinationDefinitionFromDestination(sync.getDestinationId());
} catch (Exception e) {
} catch (final Exception e) {
throw new RuntimeException(e);
}
}
Expand Down Expand Up @@ -175,7 +179,7 @@ public DestinationConnection getDestinationConnection(final UUID destinationId)
return persistence.getConfig(ConfigSchema.DESTINATION_CONNECTION, destinationId.toString(), DestinationConnection.class);
}

public void writeDestinationConnection(DestinationConnection destinationConnection) throws JsonValidationException, IOException {
public void writeDestinationConnection(final DestinationConnection destinationConnection) throws JsonValidationException, IOException {
persistence.writeConfig(ConfigSchema.DESTINATION_CONNECTION, destinationConnection.getDestinationId().toString(), destinationConnection);
}

Expand Down Expand Up @@ -207,7 +211,7 @@ public List<StandardSyncOperation> listStandardSyncOperations() throws IOExcepti
return persistence.listConfigs(ConfigSchema.STANDARD_SYNC_OPERATION, StandardSyncOperation.class);
}

public <T> void replaceAllConfigs(Map<AirbyteConfig, Stream<T>> configs, boolean dryRun) throws IOException {
public <T> void replaceAllConfigs(final Map<AirbyteConfig, Stream<T>> configs, final boolean dryRun) throws IOException {
persistence.replaceAllConfigs(configs, dryRun);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -45,13 +45,15 @@
import io.airbyte.notification.NotificationClient;
import io.airbyte.server.converters.NotificationConverter;
import io.airbyte.server.errors.IdNotFoundKnownException;
import io.airbyte.server.errors.InternalServerKnownException;
import io.airbyte.server.errors.ValueConflictKnownException;
import io.airbyte.validation.json.JsonValidationException;
import java.io.IOException;
import java.util.List;
import java.util.UUID;
import java.util.function.Supplier;
import java.util.stream.Collectors;
import org.apache.commons.lang3.RandomStringUtils;

public class WorkspacesHandler {

Expand Down Expand Up @@ -83,7 +85,7 @@ public WorkspacesHandler(final ConfigRepository configRepository,
}

public WorkspaceRead createWorkspace(final WorkspaceCreate workspaceCreate)
throws JsonValidationException, IOException, ConfigNotFoundException, ValueConflictKnownException {
throws JsonValidationException, IOException, ValueConflictKnownException {

final String email = workspaceCreate.getEmail();
final Boolean anonymousDataCollection = workspaceCreate.getAnonymousDataCollection();
Expand All @@ -94,7 +96,7 @@ public WorkspaceRead createWorkspace(final WorkspaceCreate workspaceCreate)
.withWorkspaceId(uuidSupplier.get())
.withCustomerId(uuidSupplier.get())
.withName(workspaceCreate.getName())
.withSlug(slugify.slugify(workspaceCreate.getName()))
.withSlug(generateUniqueSlug(workspaceCreate.getName()))
.withInitialSetupComplete(false)
.withAnonymousDataCollection(anonymousDataCollection != null ? anonymousDataCollection : false)
.withNews(news != null ? news : false)
Expand All @@ -107,14 +109,6 @@ public WorkspaceRead createWorkspace(final WorkspaceCreate workspaceCreate)
workspace.withEmail(email);
}

try {
if (configRepository.getWorkspaceBySlug(workspace.getSlug(), false) != null) {
throw new ValueConflictKnownException("A workspace already exists with the same name");
}
} catch (ConfigNotFoundException e) {
// no workspace exists with the slug, lets create ours
}

configRepository.writeStandardWorkspace(workspace);

return buildWorkspaceRead(workspace);
Expand All @@ -126,17 +120,17 @@ public void deleteWorkspace(final WorkspaceIdRequestBody workspaceIdRequestBody)
final StandardWorkspace persistedWorkspace = configRepository.getStandardWorkspace(workspaceIdRequestBody.getWorkspaceId(), false);

// disable all connections associated with this workspace
for (ConnectionRead connectionRead : connectionsHandler.listConnectionsForWorkspace(workspaceIdRequestBody).getConnections()) {
for (final ConnectionRead connectionRead : connectionsHandler.listConnectionsForWorkspace(workspaceIdRequestBody).getConnections()) {
connectionsHandler.deleteConnection(connectionRead);
}

// disable all destinations associated with this workspace
for (DestinationRead destinationRead : destinationHandler.listDestinationsForWorkspace(workspaceIdRequestBody).getDestinations()) {
for (final DestinationRead destinationRead : destinationHandler.listDestinationsForWorkspace(workspaceIdRequestBody).getDestinations()) {
destinationHandler.deleteDestination(destinationRead);
}

// disable all sources associated with this workspace
for (SourceRead sourceRead : sourceHandler.listSourcesForWorkspace(workspaceIdRequestBody).getSources()) {
for (final SourceRead sourceRead : sourceHandler.listSourcesForWorkspace(workspaceIdRequestBody).getSources()) {
sourceHandler.deleteSource(sourceRead);
}

Expand All @@ -151,21 +145,22 @@ public WorkspaceReadList listWorkspaces() throws JsonValidationException, IOExce
return new WorkspaceReadList().workspaces(reads);
}

public WorkspaceRead getWorkspace(WorkspaceIdRequestBody workspaceIdRequestBody)
public WorkspaceRead getWorkspace(final WorkspaceIdRequestBody workspaceIdRequestBody)
throws JsonValidationException, IOException, ConfigNotFoundException {
final UUID workspaceId = workspaceIdRequestBody.getWorkspaceId();
final StandardWorkspace workspace = configRepository.getStandardWorkspace(workspaceId, false);
return buildWorkspaceRead(workspace);
}

@SuppressWarnings("unused")
public WorkspaceRead getWorkspaceBySlug(SlugRequestBody slugRequestBody) throws JsonValidationException, IOException, ConfigNotFoundException {
public WorkspaceRead getWorkspaceBySlug(final SlugRequestBody slugRequestBody)
throws JsonValidationException, IOException, ConfigNotFoundException {
// for now we assume there is one workspace and it has a default uuid.
final StandardWorkspace workspace = configRepository.getWorkspaceBySlug(slugRequestBody.getSlug(), false);
return buildWorkspaceRead(workspace);
}

public WorkspaceRead updateWorkspace(WorkspaceUpdate workspaceUpdate) throws ConfigNotFoundException, IOException, JsonValidationException {
public WorkspaceRead updateWorkspace(final WorkspaceUpdate workspaceUpdate) throws ConfigNotFoundException, IOException, JsonValidationException {
final UUID workspaceId = workspaceUpdate.getWorkspaceId();

final StandardWorkspace persistedWorkspace = configRepository.getStandardWorkspace(workspaceId, false);
Expand All @@ -190,28 +185,51 @@ public WorkspaceRead updateWorkspace(WorkspaceUpdate workspaceUpdate) throws Con
return buildWorkspaceReadFromId(workspaceUpdate.getWorkspaceId());
}

public NotificationRead tryNotification(Notification notification) {
public NotificationRead tryNotification(final Notification notification) {
try {
final NotificationClient notificationClient = NotificationClient.createNotificationClient(NotificationConverter.toConfig(notification));
final String message = String.format("Hello World! This is a test from Airbyte to try %s notification settings",
notification.getNotificationType());
if (notificationClient.notify(message)) {
return new NotificationRead().status(StatusEnum.SUCCEEDED);
}
} catch (IllegalArgumentException e) {
} catch (final IllegalArgumentException e) {
throw new IdNotFoundKnownException(e.getMessage(), notification.getNotificationType().name());
} catch (IOException | InterruptedException e) {
} catch (final IOException | InterruptedException e) {
return new NotificationRead().status(StatusEnum.FAILED).message(e.getMessage());
}
return new NotificationRead().status(StatusEnum.FAILED);
}

private WorkspaceRead buildWorkspaceReadFromId(UUID workspaceId) throws ConfigNotFoundException, IOException, JsonValidationException {
private WorkspaceRead buildWorkspaceReadFromId(final UUID workspaceId) throws ConfigNotFoundException, IOException, JsonValidationException {
final StandardWorkspace workspace = configRepository.getStandardWorkspace(workspaceId, false);
return buildWorkspaceRead(workspace);
}

private static WorkspaceRead buildWorkspaceRead(StandardWorkspace workspace) {
private String generateUniqueSlug(final String workspaceName) throws JsonValidationException, IOException {
final String proposedSlug = slugify.slugify(workspaceName);

// todo (cgardens) - this is going to be too expensive once there are too many workspaces. needs to
// be replaced with an actual sql query. e.g. SELECT COUNT(*) WHERE slug=%s;
boolean isSlugUsed = configRepository.getWorkspaceBySlugOptional(proposedSlug, true).isPresent();
String resolvedSlug = proposedSlug;
final int MAX_ATTEMPTS = 10;
int count = 0;
while (isSlugUsed) {
// todo (cgardens) - this is still susceptible to a race condition where we randomly generate the
// same slug in two different threads. this should be very unlikely. we can fix this by exposing
// database transaction, but that is not something we can do quickly.
resolvedSlug = proposedSlug + "-" + RandomStringUtils.randomAlphabetic(8);
isSlugUsed = configRepository.getWorkspaceBySlugOptional(resolvedSlug, true).isPresent();
if (count++ > MAX_ATTEMPTS) {
throw new InternalServerKnownException(String.format("could not generate a valid slug after %s tries.", MAX_ATTEMPTS));
}
}

return resolvedSlug;
}

private static WorkspaceRead buildWorkspaceRead(final StandardWorkspace workspace) {
return new WorkspaceRead()
.workspaceId(workspace.getWorkspaceId())
.customerId(workspace.getCustomerId())
Expand Down
Loading

0 comments on commit 2fd311e

Please sign in to comment.