From b4583a2b7c34b38c8d8f8738f225d57cc1b3b863 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ren=C3=A9=20Pfeuffer?= Date: Mon, 14 Sep 2020 18:52:52 +0200 Subject: [PATCH] Introduce manager interface for namespaces --- .../java/sonia/scm/repository/Namespace.java | 104 +++++++++++++++ .../scm/repository/NamespaceManager.java | 54 ++++++++ .../repository/DefaultNamespaceManager.java | 78 ++++++++++++ .../repository/DefaultRepositoryManager.java | 4 + .../sonia/scm/repository/NamespaceDao.java | 49 ++++++++ .../DefaultNamespaceManagerTest.java | 119 ++++++++++++++++++ 6 files changed, 408 insertions(+) create mode 100644 scm-core/src/main/java/sonia/scm/repository/Namespace.java create mode 100644 scm-core/src/main/java/sonia/scm/repository/NamespaceManager.java create mode 100644 scm-webapp/src/main/java/sonia/scm/repository/DefaultNamespaceManager.java create mode 100644 scm-webapp/src/main/java/sonia/scm/repository/NamespaceDao.java create mode 100644 scm-webapp/src/test/java/sonia/scm/repository/DefaultNamespaceManagerTest.java diff --git a/scm-core/src/main/java/sonia/scm/repository/Namespace.java b/scm-core/src/main/java/sonia/scm/repository/Namespace.java new file mode 100644 index 0000000000..0826a4f1ea --- /dev/null +++ b/scm-core/src/main/java/sonia/scm/repository/Namespace.java @@ -0,0 +1,104 @@ +/* + * MIT License + * + * Copyright (c) 2020-present Cloudogu GmbH and Contributors + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package sonia.scm.repository; + +import org.apache.commons.lang.builder.EqualsBuilder; +import org.apache.commons.lang.builder.HashCodeBuilder; + +import java.util.Collection; +import java.util.HashSet; +import java.util.Set; + +import static java.util.Collections.unmodifiableCollection; + +public class Namespace implements Cloneable { + + private String namespace; + private Set permissions = new HashSet<>(); + + public Namespace(String namespace) { + this.namespace = namespace; + } + + /** + * Constructor for JaxB, only. + */ + Namespace() { + } + + public String getNamespace() { + return namespace; + } + + public Collection getPermissions() { + return unmodifiableCollection(permissions); + } + + public void setPermissions(Collection permissions) { + this.permissions.clear(); + this.permissions.addAll(permissions); + } + + public void addPermission(RepositoryPermission newPermission) { + this.permissions.add(newPermission); + } + + public boolean removePermission(RepositoryPermission permission) { + return this.permissions.remove(permission); + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + + if (!(o instanceof Namespace)) return false; + + Namespace namespace1 = (Namespace) o; + + return new EqualsBuilder() + .append(namespace, namespace1.namespace) + .append(permissions, namespace1.permissions) + .isEquals(); + } + + @Override + public int hashCode() { + return new HashCodeBuilder(17, 37) + .append(namespace) + .append(permissions) + .toHashCode(); + } + + @Override + public Namespace clone() { + try { + Namespace clone = (Namespace) super.clone(); + clone.permissions = new HashSet<>(permissions); + return clone; + } catch (CloneNotSupportedException ex) { + throw new RuntimeException(ex); + } + } +} diff --git a/scm-core/src/main/java/sonia/scm/repository/NamespaceManager.java b/scm-core/src/main/java/sonia/scm/repository/NamespaceManager.java new file mode 100644 index 0000000000..bdd8faac7d --- /dev/null +++ b/scm-core/src/main/java/sonia/scm/repository/NamespaceManager.java @@ -0,0 +1,54 @@ +/* + * MIT License + * + * Copyright (c) 2020-present Cloudogu GmbH and Contributors + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package sonia.scm.repository; + +import java.util.Collection; +import java.util.Optional; + +public interface NamespaceManager { + + /** + * Returns the Namespace with the given name. + * + * @param namespace The name of the requested namespace. + * @return Optional with the namespace for the given name, or an empty Optional if there is no such namespace + * (that is, there is no repository with this namespace). + */ + Optional get(String namespace); + + /** + * Returns a {@link java.util.Collection} of all namespaces. + * + * @return all namespaces + */ + Collection getAll(); + + /** + * Modifies the given namespace. + * + * @param namespace The namespace to be modified. + */ + void modify(Namespace namespace); +} diff --git a/scm-webapp/src/main/java/sonia/scm/repository/DefaultNamespaceManager.java b/scm-webapp/src/main/java/sonia/scm/repository/DefaultNamespaceManager.java new file mode 100644 index 0000000000..e0d6ec9117 --- /dev/null +++ b/scm-webapp/src/main/java/sonia/scm/repository/DefaultNamespaceManager.java @@ -0,0 +1,78 @@ +/* + * MIT License + * + * Copyright (c) 2020-present Cloudogu GmbH and Contributors + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package sonia.scm.repository; + +import javax.inject.Inject; +import java.util.Collection; +import java.util.Optional; +import java.util.stream.Collectors; + +import static sonia.scm.ContextEntry.ContextBuilder.entity; +import static sonia.scm.NotFoundException.notFound; + +public class DefaultNamespaceManager implements NamespaceManager { + + private final RepositoryManager repositoryManager; + private final NamespaceDao dao; + + @Inject + public DefaultNamespaceManager(RepositoryManager repositoryManager, NamespaceDao dao) { + this.repositoryManager = repositoryManager; + this.dao = dao; + } + + @Override + public Optional get(String namespace) { + return repositoryManager + .getAllNamespaces() + .stream() + .filter(n -> n.equals(namespace)) + .map(this::createNamespaceForName) + .findFirst(); + } + + @Override + public Collection getAll() { + return repositoryManager + .getAllNamespaces() + .stream() + .map(this::createNamespaceForName) + .collect(Collectors.toList()); + } + + @Override + public void modify(Namespace namespace) { + if (!get(namespace.getNamespace()).isPresent()) { + throw notFound(entity("Namespace", namespace.getNamespace())); + } + dao.add(namespace); + } + + private Namespace createNamespaceForName(String namespace) { + return dao.get(namespace) + .map(Namespace::clone) + .orElse(new Namespace(namespace)); + } +} diff --git a/scm-webapp/src/main/java/sonia/scm/repository/DefaultRepositoryManager.java b/scm-webapp/src/main/java/sonia/scm/repository/DefaultRepositoryManager.java index c75f54c3fb..075fcca82a 100644 --- a/scm-webapp/src/main/java/sonia/scm/repository/DefaultRepositoryManager.java +++ b/scm-webapp/src/main/java/sonia/scm/repository/DefaultRepositoryManager.java @@ -331,6 +331,10 @@ public class DefaultRepositoryManager extends AbstractRepositoryManager { return getAll(null, start, limit); } + /** + * @deprecated Use {@link NamespaceManager#getAll()} instead. + */ + @Deprecated @Override public Collection getAllNamespaces() { return getAll().stream() diff --git a/scm-webapp/src/main/java/sonia/scm/repository/NamespaceDao.java b/scm-webapp/src/main/java/sonia/scm/repository/NamespaceDao.java new file mode 100644 index 0000000000..a566ba879a --- /dev/null +++ b/scm-webapp/src/main/java/sonia/scm/repository/NamespaceDao.java @@ -0,0 +1,49 @@ +/* + * MIT License + * + * Copyright (c) 2020-present Cloudogu GmbH and Contributors + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package sonia.scm.repository; + +import sonia.scm.store.DataStore; +import sonia.scm.store.DataStoreFactory; + +import javax.inject.Inject; +import java.util.Optional; + +public class NamespaceDao { + + private final DataStore store; + + @Inject + NamespaceDao(DataStoreFactory storeFactory) { + this.store = storeFactory.withType(Namespace.class).withName("namespaces").build(); + } + + public Optional get(String namespace) { + return store.getOptional(namespace); + } + + public void add(Namespace namespace) { + store.put(namespace.getNamespace(), namespace); + } +} diff --git a/scm-webapp/src/test/java/sonia/scm/repository/DefaultNamespaceManagerTest.java b/scm-webapp/src/test/java/sonia/scm/repository/DefaultNamespaceManagerTest.java new file mode 100644 index 0000000000..736e1a90d1 --- /dev/null +++ b/scm-webapp/src/test/java/sonia/scm/repository/DefaultNamespaceManagerTest.java @@ -0,0 +1,119 @@ +/* + * MIT License + * + * Copyright (c) 2020-present Cloudogu GmbH and Contributors + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package sonia.scm.repository; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; +import sonia.scm.store.InMemoryDataStore; +import sonia.scm.store.InMemoryDataStoreFactory; + +import java.util.Collection; +import java.util.Optional; + +import static java.util.Arrays.asList; +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.Mockito.when; + + +@ExtendWith(MockitoExtension.class) +class DefaultNamespaceManagerTest { + + @Mock + RepositoryManager repositoryManager; + + Namespace life; + + NamespaceDao dao; + DefaultNamespaceManager manager; + private Namespace universe; + private Namespace rest; + + @BeforeEach + void mockExistingNamespaces() { + dao = new NamespaceDao(new InMemoryDataStoreFactory(new InMemoryDataStore())); + manager = new DefaultNamespaceManager(repositoryManager, dao); + + when(repositoryManager.getAllNamespaces()).thenReturn(asList("life", "universe", "rest")); + + life = new Namespace("life"); + RepositoryPermission lifePermission = new RepositoryPermission("humans", "OWNER", true); + life.addPermission(lifePermission); + dao.add(life); + + universe = new Namespace("universe"); + rest = new Namespace("rest"); + } + + @Test + void shouldCreateEmptyOptionalIfNamespaceDoesNotExist() { + Optional namespace = manager.get("dolphins"); + + assertThat(namespace).isEmpty(); + } + + @Test + void shouldCreateNewNamespaceObjectIfNotInStore() { + Namespace namespace = manager.get("universe").orElse(null); + + assertThat(namespace).isEqualTo(universe); + assertThat(namespace.getPermissions()).isEmpty(); + } + + @Test + void shouldEnrichExistingNamespaceWithPermissions() { + Namespace namespace = manager.get("life").orElse(null); + + assertThat(namespace.getPermissions()).containsExactly(life.getPermissions().toArray(new RepositoryPermission[0])); + } + + @Test + void shouldEnrichExistingNamespaceWithPermissionsInGetAll() { + Collection namespaces = manager.getAll(); + + assertThat(namespaces).containsExactly( + life, + universe, + rest + ); + Namespace foundLifeNamespace = namespaces.stream().filter(namespace -> namespace.getNamespace().equals("life")).findFirst().get(); + assertThat( + foundLifeNamespace.getPermissions()).containsExactly(life.getPermissions().toArray(new RepositoryPermission[0])); + } + + @Test + void shouldModifyExistingNamespaceWithPermissions() { + Namespace modifiedNamespace = manager.get("life").get(); + + modifiedNamespace.setPermissions(asList(new RepositoryPermission("Arthur Dent", "READ", false))); + manager.modify(modifiedNamespace); + + Namespace newLife = manager.get("life").get(); + + assertThat(newLife).isEqualTo(modifiedNamespace); + } +}