mirror of
https://github.com/scm-manager/scm-manager.git
synced 2025-11-15 09:46:16 +01:00
Sorted autocomplete (#1918)
Users, groups, repositories and repository roles have been sorted in the rest layer by default if no other sort option was given. In the layers "below" (aka the manager classes or the dao), the collections have been unsorted. This led to the effect, that the autocomplete resource, which did not sort all values beforehand, returned unsorted results. As a sideeffect, direct matches for an input could occur at a random position or not at all (as reported in #1695), when there were enough other matches. With this pull request the databases for users, groups, repositories and repository roles will use instances of TreeMap instead of LinkedHashMap internally, so that these values are sorted implicitly (by id respectively name for users, groups and repository roles and namespace/name for repositories). Due to this change the default sort applied in the rest layer could be removed.
This commit is contained in:
@@ -21,25 +21,20 @@
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
* SOFTWARE.
|
||||
*/
|
||||
|
||||
package sonia.scm.group.xml;
|
||||
|
||||
//~--- non-JDK imports --------------------------------------------------------
|
||||
package sonia.scm.group.xml;
|
||||
|
||||
import sonia.scm.group.Group;
|
||||
import sonia.scm.xml.XmlDatabase;
|
||||
|
||||
//~--- JDK imports ------------------------------------------------------------
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import javax.xml.bind.annotation.XmlAccessType;
|
||||
import javax.xml.bind.annotation.XmlAccessorType;
|
||||
import javax.xml.bind.annotation.XmlElement;
|
||||
import javax.xml.bind.annotation.XmlRootElement;
|
||||
import javax.xml.bind.annotation.adapters.XmlJavaTypeAdapter;
|
||||
import java.util.Collection;
|
||||
import java.util.Map;
|
||||
import java.util.TreeMap;
|
||||
|
||||
/**
|
||||
*
|
||||
@@ -190,7 +185,7 @@ public class XmlGroupDatabase implements XmlDatabase<Group>
|
||||
/** Field description */
|
||||
@XmlJavaTypeAdapter(XmlGroupMapAdapter.class)
|
||||
@XmlElement(name = "groups")
|
||||
private Map<String, Group> groupMap = new LinkedHashMap<>();
|
||||
private Map<String, Group> groupMap = new TreeMap<>();
|
||||
|
||||
/** Field description */
|
||||
private Long lastModified;
|
||||
|
||||
@@ -21,19 +21,14 @@
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
* SOFTWARE.
|
||||
*/
|
||||
|
||||
package sonia.scm.group.xml;
|
||||
|
||||
//~--- non-JDK imports --------------------------------------------------------
|
||||
package sonia.scm.group.xml;
|
||||
|
||||
import sonia.scm.group.Group;
|
||||
|
||||
//~--- JDK imports ------------------------------------------------------------
|
||||
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import javax.xml.bind.annotation.adapters.XmlAdapter;
|
||||
import java.util.Map;
|
||||
import java.util.TreeMap;
|
||||
|
||||
/**
|
||||
*
|
||||
@@ -72,7 +67,7 @@ public class XmlGroupMapAdapter
|
||||
@Override
|
||||
public Map<String, Group> unmarshal(XmlGroupList groups) throws Exception
|
||||
{
|
||||
Map<String, Group> groupMap = new LinkedHashMap<>();
|
||||
Map<String, Group> groupMap = new TreeMap<>();
|
||||
|
||||
for (Group group : groups)
|
||||
{
|
||||
|
||||
@@ -41,8 +41,13 @@ import javax.inject.Inject;
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Path;
|
||||
import java.util.Collection;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.TreeMap;
|
||||
import java.util.concurrent.locks.Lock;
|
||||
import java.util.concurrent.locks.ReadWriteLock;
|
||||
import java.util.concurrent.locks.ReentrantReadWriteLock;
|
||||
import java.util.function.Supplier;
|
||||
|
||||
/**
|
||||
* @author Sebastian Sdorra
|
||||
@@ -50,7 +55,6 @@ import java.util.concurrent.ConcurrentHashMap;
|
||||
@Singleton
|
||||
public class XmlRepositoryDAO implements RepositoryDAO {
|
||||
|
||||
|
||||
private final MetadataStore metadataStore = new MetadataStore();
|
||||
|
||||
private final PathBasedRepositoryLocationResolver repositoryLocationResolver;
|
||||
@@ -59,6 +63,7 @@ public class XmlRepositoryDAO implements RepositoryDAO {
|
||||
|
||||
private final Map<String, Repository> byId;
|
||||
private final Map<NamespaceAndName, Repository> byNamespaceAndName;
|
||||
private final ReadWriteLock byNamespaceLock = new ReentrantReadWriteLock();
|
||||
|
||||
@Inject
|
||||
public XmlRepositoryDAO(PathBasedRepositoryLocationResolver repositoryLocationResolver, FileSystem fileSystem, RepositoryExportingCheck repositoryExportingCheck) {
|
||||
@@ -66,18 +71,20 @@ public class XmlRepositoryDAO implements RepositoryDAO {
|
||||
this.fileSystem = fileSystem;
|
||||
this.repositoryExportingCheck = repositoryExportingCheck;
|
||||
|
||||
this.byId = new ConcurrentHashMap<>();
|
||||
this.byNamespaceAndName = new ConcurrentHashMap<>();
|
||||
this.byId = new HashMap<>();
|
||||
this.byNamespaceAndName = new TreeMap<>();
|
||||
|
||||
init();
|
||||
}
|
||||
|
||||
private void init() {
|
||||
RepositoryLocationResolver.RepositoryLocationResolverInstance<Path> pathRepositoryLocationResolverInstance = repositoryLocationResolver.create(Path.class);
|
||||
pathRepositoryLocationResolverInstance.forAllLocations((repositoryId, repositoryPath) -> {
|
||||
Repository repository = metadataStore.read(repositoryPath);
|
||||
byNamespaceAndName.put(repository.getNamespaceAndName(), repository);
|
||||
byId.put(repositoryId, repository);
|
||||
withWriteLockedMaps(() -> {
|
||||
RepositoryLocationResolver.RepositoryLocationResolverInstance<Path> pathRepositoryLocationResolverInstance = repositoryLocationResolver.create(Path.class);
|
||||
pathRepositoryLocationResolverInstance.forAllLocations((repositoryId, repositoryPath) -> {
|
||||
Repository repository = metadataStore.read(repositoryPath);
|
||||
byNamespaceAndName.put(repository.getNamespaceAndName(), repository);
|
||||
byId.put(repositoryId, repository);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
@@ -106,38 +113,40 @@ public class XmlRepositoryDAO implements RepositoryDAO {
|
||||
throw new InternalRepositoryException(repository, "failed to create filesystem", e);
|
||||
}
|
||||
|
||||
byId.put(repository.getId(), clone);
|
||||
byNamespaceAndName.put(repository.getNamespaceAndName(), clone);
|
||||
withWriteLockedMaps(() -> {
|
||||
byId.put(repository.getId(), clone);
|
||||
byNamespaceAndName.put(repository.getNamespaceAndName(), clone);
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean contains(Repository repository) {
|
||||
return byId.containsKey(repository.getId());
|
||||
return withReadLockedMaps(() -> byId.containsKey(repository.getId()));
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean contains(NamespaceAndName namespaceAndName) {
|
||||
return byNamespaceAndName.containsKey(namespaceAndName);
|
||||
return withReadLockedMaps(() -> byNamespaceAndName.containsKey(namespaceAndName));
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean contains(String id) {
|
||||
return byId.containsKey(id);
|
||||
return withReadLockedMaps(() -> byId.containsKey(id));
|
||||
}
|
||||
|
||||
@Override
|
||||
public Repository get(NamespaceAndName namespaceAndName) {
|
||||
return byNamespaceAndName.get(namespaceAndName);
|
||||
return withReadLockedMaps(() -> byNamespaceAndName.get(namespaceAndName));
|
||||
}
|
||||
|
||||
@Override
|
||||
public Repository get(String id) {
|
||||
return byId.get(id);
|
||||
return withReadLockedMaps(() -> byId.get(id));
|
||||
}
|
||||
|
||||
@Override
|
||||
public Collection<Repository> getAll() {
|
||||
return ImmutableList.copyOf(byNamespaceAndName.values());
|
||||
return withReadLockedMaps(() -> ImmutableList.copyOf(byNamespaceAndName.values()));
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -147,14 +156,14 @@ public class XmlRepositoryDAO implements RepositoryDAO {
|
||||
throw new StoreReadOnlyException(repository);
|
||||
}
|
||||
|
||||
synchronized (this) {
|
||||
withWriteLockedMaps(() -> {
|
||||
// remove old namespaceAndName from map, in case of rename
|
||||
Repository prev = byId.put(clone.getId(), clone);
|
||||
if (prev != null) {
|
||||
byNamespaceAndName.remove(prev.getNamespaceAndName());
|
||||
}
|
||||
byNamespaceAndName.put(clone.getNamespaceAndName(), clone);
|
||||
}
|
||||
});
|
||||
|
||||
Path repositoryPath = repositoryLocationResolver
|
||||
.create(Path.class)
|
||||
@@ -164,8 +173,10 @@ public class XmlRepositoryDAO implements RepositoryDAO {
|
||||
}
|
||||
|
||||
private boolean mustNotModifyRepository(Repository clone) {
|
||||
return clone.isArchived() && byId.get(clone.getId()).isArchived()
|
||||
|| repositoryExportingCheck.isExporting(clone);
|
||||
return withReadLockedMaps(() ->
|
||||
clone.isArchived() && byId.get(clone.getId()).isArchived()
|
||||
|| repositoryExportingCheck.isExporting(clone)
|
||||
);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -173,14 +184,13 @@ public class XmlRepositoryDAO implements RepositoryDAO {
|
||||
if (repository.isArchived() || repositoryExportingCheck.isExporting(repository)) {
|
||||
throw new StoreReadOnlyException(repository);
|
||||
}
|
||||
Path path;
|
||||
synchronized (this) {
|
||||
Path path = withWriteLockedMaps(() -> {
|
||||
Repository prev = byId.remove(repository.getId());
|
||||
if (prev != null) {
|
||||
byNamespaceAndName.remove(prev.getNamespaceAndName());
|
||||
}
|
||||
path = repositoryLocationResolver.remove(repository.getId());
|
||||
}
|
||||
return repositoryLocationResolver.remove(repository.getId());
|
||||
});
|
||||
|
||||
try {
|
||||
fileSystem.destroy(path.toFile());
|
||||
@@ -201,8 +211,40 @@ public class XmlRepositoryDAO implements RepositoryDAO {
|
||||
|
||||
public void refresh() {
|
||||
repositoryLocationResolver.refresh();
|
||||
byNamespaceAndName.clear();
|
||||
byId.clear();
|
||||
withWriteLockedMaps(() -> {
|
||||
byNamespaceAndName.clear();
|
||||
byId.clear();
|
||||
});
|
||||
init();
|
||||
}
|
||||
|
||||
private void withWriteLockedMaps(Runnable runnable) {
|
||||
Lock lock = byNamespaceLock.writeLock();
|
||||
lock.lock();
|
||||
try {
|
||||
runnable.run();
|
||||
} finally {
|
||||
lock.unlock();
|
||||
}
|
||||
}
|
||||
|
||||
private <T> T withWriteLockedMaps(Supplier<T> runnable) {
|
||||
Lock lock = byNamespaceLock.writeLock();
|
||||
lock.lock();
|
||||
try {
|
||||
return runnable.get();
|
||||
} finally {
|
||||
lock.unlock();
|
||||
}
|
||||
}
|
||||
|
||||
private <T> T withReadLockedMaps(Supplier<T> runnable) {
|
||||
Lock lock = byNamespaceLock.readLock();
|
||||
lock.lock();
|
||||
try {
|
||||
return runnable.get();
|
||||
} finally {
|
||||
lock.unlock();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -21,7 +21,7 @@
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
* SOFTWARE.
|
||||
*/
|
||||
|
||||
|
||||
package sonia.scm.repository.xml;
|
||||
|
||||
import sonia.scm.repository.RepositoryRole;
|
||||
@@ -33,8 +33,8 @@ import javax.xml.bind.annotation.XmlElement;
|
||||
import javax.xml.bind.annotation.XmlRootElement;
|
||||
import javax.xml.bind.annotation.adapters.XmlJavaTypeAdapter;
|
||||
import java.util.Collection;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.Map;
|
||||
import java.util.TreeMap;
|
||||
|
||||
@XmlRootElement(name = "user-db")
|
||||
@XmlAccessorType(XmlAccessType.FIELD)
|
||||
@@ -45,7 +45,7 @@ public class XmlRepositoryRoleDatabase implements XmlDatabase<RepositoryRole> {
|
||||
|
||||
@XmlJavaTypeAdapter(XmlRepositoryRoleMapAdapter.class)
|
||||
@XmlElement(name = "roles")
|
||||
private Map<String, RepositoryRole> roleMap = new LinkedHashMap<>();
|
||||
private Map<String, RepositoryRole> roleMap = new TreeMap<>();
|
||||
|
||||
public XmlRepositoryRoleDatabase() {
|
||||
long c = System.currentTimeMillis();
|
||||
|
||||
@@ -21,14 +21,14 @@
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
* SOFTWARE.
|
||||
*/
|
||||
|
||||
|
||||
package sonia.scm.repository.xml;
|
||||
|
||||
import sonia.scm.repository.RepositoryRole;
|
||||
|
||||
import javax.xml.bind.annotation.adapters.XmlAdapter;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.Map;
|
||||
import java.util.TreeMap;
|
||||
|
||||
public class XmlRepositoryRoleMapAdapter
|
||||
extends XmlAdapter<XmlRepositoryRoleList, Map<String, RepositoryRole>> {
|
||||
@@ -40,7 +40,7 @@ public class XmlRepositoryRoleMapAdapter
|
||||
|
||||
@Override
|
||||
public Map<String, RepositoryRole> unmarshal(XmlRepositoryRoleList roles) {
|
||||
Map<String, RepositoryRole> roleMap = new LinkedHashMap<>();
|
||||
Map<String, RepositoryRole> roleMap = new TreeMap<>();
|
||||
|
||||
for (RepositoryRole role : roles) {
|
||||
roleMap.put(role.getName(), role);
|
||||
|
||||
@@ -21,25 +21,20 @@
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
* SOFTWARE.
|
||||
*/
|
||||
|
||||
package sonia.scm.user.xml;
|
||||
|
||||
//~--- non-JDK imports --------------------------------------------------------
|
||||
package sonia.scm.user.xml;
|
||||
|
||||
import sonia.scm.user.User;
|
||||
import sonia.scm.xml.XmlDatabase;
|
||||
|
||||
//~--- JDK imports ------------------------------------------------------------
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import javax.xml.bind.annotation.XmlAccessType;
|
||||
import javax.xml.bind.annotation.XmlAccessorType;
|
||||
import javax.xml.bind.annotation.XmlElement;
|
||||
import javax.xml.bind.annotation.XmlRootElement;
|
||||
import javax.xml.bind.annotation.adapters.XmlJavaTypeAdapter;
|
||||
import java.util.Collection;
|
||||
import java.util.Map;
|
||||
import java.util.TreeMap;
|
||||
|
||||
/**
|
||||
*
|
||||
@@ -193,5 +188,5 @@ public class XmlUserDatabase implements XmlDatabase<User>
|
||||
/** Field description */
|
||||
@XmlJavaTypeAdapter(XmlUserMapAdapter.class)
|
||||
@XmlElement(name = "users")
|
||||
private Map<String, User> userMap = new LinkedHashMap<>();
|
||||
private Map<String, User> userMap = new TreeMap<>();
|
||||
}
|
||||
|
||||
@@ -21,19 +21,14 @@
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
* SOFTWARE.
|
||||
*/
|
||||
|
||||
package sonia.scm.user.xml;
|
||||
|
||||
//~--- non-JDK imports --------------------------------------------------------
|
||||
package sonia.scm.user.xml;
|
||||
|
||||
import sonia.scm.user.User;
|
||||
|
||||
//~--- JDK imports ------------------------------------------------------------
|
||||
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import javax.xml.bind.annotation.adapters.XmlAdapter;
|
||||
import java.util.Map;
|
||||
import java.util.TreeMap;
|
||||
|
||||
/**
|
||||
*
|
||||
@@ -72,7 +67,7 @@ public class XmlUserMapAdapter
|
||||
@Override
|
||||
public Map<String, User> unmarshal(XmlUserList users) throws Exception
|
||||
{
|
||||
Map<String, User> userMap = new LinkedHashMap<>();
|
||||
Map<String, User> userMap = new TreeMap<>();
|
||||
|
||||
for (User user : users)
|
||||
{
|
||||
|
||||
@@ -21,7 +21,7 @@
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
* SOFTWARE.
|
||||
*/
|
||||
|
||||
|
||||
package sonia.scm.xml;
|
||||
|
||||
//~--- non-JDK imports --------------------------------------------------------
|
||||
@@ -45,7 +45,7 @@ import java.util.Collection;
|
||||
* @param <T>
|
||||
*/
|
||||
public abstract class AbstractXmlDAO<I extends ModelObject,
|
||||
T extends XmlDatabase> implements GenericDAO<I>
|
||||
T extends XmlDatabase<I>> implements GenericDAO<I>
|
||||
{
|
||||
|
||||
/** Field description */
|
||||
|
||||
Reference in New Issue
Block a user