mirror of
https://github.com/scm-manager/scm-manager.git
synced 2026-03-22 20:11:38 +01:00
Add queryable store with SQLite implementation
This adds the new "queryable store" API, that allows complex queries and is backed by SQLite. This new API can be used for entities annotated with the new QueryableType annotation.
This commit is contained in:
@@ -77,4 +77,12 @@ public interface ExtensionProcessor
|
||||
default Iterable<Class<?>> getIndexedTypes() {
|
||||
return emptySet();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns all queryable types.
|
||||
* @since 3.7.0
|
||||
*/
|
||||
default Iterable<QueryableTypeDescriptor> getQueryableTypes() {
|
||||
return emptySet();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -27,7 +27,7 @@ import lombok.ToString;
|
||||
import java.util.HashSet;
|
||||
|
||||
@Getter
|
||||
@ToString
|
||||
@ToString(callSuper = true)
|
||||
@EqualsAndHashCode(callSuper = true)
|
||||
@XmlAccessorType(XmlAccessType.FIELD)
|
||||
@NoArgsConstructor(access = AccessLevel.PACKAGE)
|
||||
|
||||
@@ -0,0 +1,39 @@
|
||||
/*
|
||||
* Copyright (c) 2020 - present Cloudogu GmbH
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify it under
|
||||
* the terms of the GNU Affero General Public License as published by the Free
|
||||
* Software Foundation, version 3.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
|
||||
* FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more
|
||||
* details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see https://www.gnu.org/licenses/.
|
||||
*/
|
||||
|
||||
package sonia.scm.plugin;
|
||||
|
||||
import jakarta.xml.bind.annotation.XmlAccessType;
|
||||
import jakarta.xml.bind.annotation.XmlAccessorType;
|
||||
import jakarta.xml.bind.annotation.XmlElement;
|
||||
import jakarta.xml.bind.annotation.adapters.XmlJavaTypeAdapter;
|
||||
import lombok.*;
|
||||
import sonia.scm.xml.XmlArrayStringAdapter;
|
||||
|
||||
@ToString(callSuper = true)
|
||||
@EqualsAndHashCode(callSuper = true)
|
||||
@XmlAccessorType(XmlAccessType.FIELD)
|
||||
@NoArgsConstructor(access = AccessLevel.PACKAGE)
|
||||
public class QueryableTypeDescriptor extends NamedClassElement {
|
||||
|
||||
@XmlElement(name = "value")
|
||||
@XmlJavaTypeAdapter(XmlArrayStringAdapter.class)
|
||||
private String[] types;
|
||||
|
||||
public String[] getTypes() {
|
||||
return types == null ? new String[0] : types;
|
||||
}
|
||||
}
|
||||
@@ -65,6 +65,9 @@ public class ScmModule {
|
||||
@XmlElement(name = "web-element")
|
||||
private Set<WebElementDescriptor> webElements;
|
||||
|
||||
@XmlElement(name = "queryable-type")
|
||||
private Set<QueryableTypeDescriptor> queryableTypes;
|
||||
|
||||
public Iterable<ClassElement> getEvents() {
|
||||
return nonNull(events);
|
||||
}
|
||||
@@ -107,12 +110,18 @@ public class ScmModule {
|
||||
|
||||
/**
|
||||
* @since 3.0.0
|
||||
|
||||
*/
|
||||
public Iterable<ConfigElement> getConfigElements() {
|
||||
return nonNull(configElements);
|
||||
}
|
||||
|
||||
/**
|
||||
* @since 3.7.0
|
||||
*/
|
||||
public Iterable<QueryableTypeDescriptor> getQueryableTypes() {
|
||||
return nonNull(queryableTypes);
|
||||
}
|
||||
|
||||
private <T> Iterable<T> nonNull(Iterable<T> iterable) {
|
||||
if (iterable == null) {
|
||||
iterable = ImmutableSet.of();
|
||||
|
||||
@@ -17,13 +17,17 @@
|
||||
package sonia.scm.repository;
|
||||
|
||||
|
||||
import lombok.Getter;
|
||||
import sonia.scm.repository.api.HookContext;
|
||||
|
||||
import java.time.Instant;
|
||||
|
||||
/**
|
||||
* Repository hook event represents an change event of a repository.
|
||||
*
|
||||
* @since 1.6
|
||||
*/
|
||||
@Getter
|
||||
public class RepositoryHookEvent
|
||||
{
|
||||
|
||||
@@ -36,6 +40,13 @@ public class RepositoryHookEvent
|
||||
/** hook type */
|
||||
private final RepositoryHookType type;
|
||||
|
||||
/**
|
||||
* creation date of the event
|
||||
*
|
||||
* @since 3.8.0
|
||||
*/
|
||||
private final Instant creationDate = Instant.now();
|
||||
|
||||
public RepositoryHookEvent(HookContext context, Repository repository,
|
||||
RepositoryHookType type)
|
||||
{
|
||||
@@ -44,24 +55,6 @@ public class RepositoryHookEvent
|
||||
this.type = type;
|
||||
}
|
||||
|
||||
|
||||
public HookContext getContext()
|
||||
{
|
||||
return context;
|
||||
}
|
||||
|
||||
|
||||
public Repository getRepository()
|
||||
{
|
||||
return repository;
|
||||
}
|
||||
|
||||
|
||||
public RepositoryHookType getType()
|
||||
{
|
||||
return type;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "RepositoryHookEvent{" +
|
||||
|
||||
21
scm-core/src/main/java/sonia/scm/store/Condition.java
Normal file
21
scm-core/src/main/java/sonia/scm/store/Condition.java
Normal file
@@ -0,0 +1,21 @@
|
||||
/*
|
||||
* Copyright (c) 2020 - present Cloudogu GmbH
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify it under
|
||||
* the terms of the GNU Affero General Public License as published by the Free
|
||||
* Software Foundation, version 3.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
|
||||
* FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more
|
||||
* details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see https://www.gnu.org/licenses/.
|
||||
*/
|
||||
|
||||
package sonia.scm.store;
|
||||
|
||||
@SuppressWarnings("unused") // We need the type 'T' here to keep type safety
|
||||
public interface Condition<T> {
|
||||
}
|
||||
38
scm-core/src/main/java/sonia/scm/store/Conditions.java
Normal file
38
scm-core/src/main/java/sonia/scm/store/Conditions.java
Normal file
@@ -0,0 +1,38 @@
|
||||
/*
|
||||
* Copyright (c) 2020 - present Cloudogu GmbH
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify it under
|
||||
* the terms of the GNU Affero General Public License as published by the Free
|
||||
* Software Foundation, version 3.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
|
||||
* FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more
|
||||
* details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see https://www.gnu.org/licenses/.
|
||||
*/
|
||||
|
||||
package sonia.scm.store;
|
||||
|
||||
public final class Conditions {
|
||||
private Conditions() {
|
||||
}
|
||||
|
||||
@SafeVarargs
|
||||
public static <T> Condition<T> and(Condition<T>... conditions) {
|
||||
return new LogicalCondition<>(LogicalOperator.AND, conditions);
|
||||
}
|
||||
|
||||
@SafeVarargs
|
||||
public static <T> Condition<T> or(Condition<T>... conditions) {
|
||||
return new LogicalCondition<>(LogicalOperator.OR, conditions);
|
||||
}
|
||||
|
||||
@SafeVarargs
|
||||
@SuppressWarnings({"unchecked", "rawtypes"})
|
||||
public static <T> Condition<T> not(Condition<T>... conditions) {
|
||||
return new LogicalCondition<>(LogicalOperator.NOT, new Condition[]{and(conditions)});
|
||||
}
|
||||
}
|
||||
50
scm-core/src/main/java/sonia/scm/store/LeafCondition.java
Normal file
50
scm-core/src/main/java/sonia/scm/store/LeafCondition.java
Normal file
@@ -0,0 +1,50 @@
|
||||
/*
|
||||
* Copyright (c) 2020 - present Cloudogu GmbH
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify it under
|
||||
* the terms of the GNU Affero General Public License as published by the Free
|
||||
* Software Foundation, version 3.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
|
||||
* FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more
|
||||
* details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see https://www.gnu.org/licenses/.
|
||||
*/
|
||||
|
||||
package sonia.scm.store;
|
||||
|
||||
import lombok.Getter;
|
||||
import lombok.Value;
|
||||
|
||||
/**
|
||||
* A <b>LeafCondition</b> is a condition builder on a {@link QueryableStore.QueryField} as part of a store statement.
|
||||
*
|
||||
* @param <T> type of the object held by the {@link QueryableStore.QueryField}
|
||||
* @param <C> value type (only required for binary operators)
|
||||
*/
|
||||
@Value
|
||||
@Getter
|
||||
public class LeafCondition<T, C> implements Condition<T> {
|
||||
|
||||
/**
|
||||
* Argument for the operator to check against.<br/>
|
||||
* Example: <em><strong>fruit</strong> EQ apple</em>
|
||||
*/
|
||||
QueryableStore.QueryField<T, ?> field;
|
||||
|
||||
/**
|
||||
* A binary (e.g. EQ, CONTAINS) or unary (e.g. NULL) operator. Binary operators require a non-null value field.<br/>
|
||||
* Example: <em>fruit <strong>EQ</strong> apple</em>, <em>fruit <em>NULL</em></em>
|
||||
*/
|
||||
Operator operator;
|
||||
|
||||
|
||||
/**
|
||||
* Value for binary operators.<br/>
|
||||
* Example: <em>fruit EQ <strong>apple</strong></em>
|
||||
*/
|
||||
C value;
|
||||
}
|
||||
32
scm-core/src/main/java/sonia/scm/store/LogicalCondition.java
Normal file
32
scm-core/src/main/java/sonia/scm/store/LogicalCondition.java
Normal file
@@ -0,0 +1,32 @@
|
||||
/*
|
||||
* Copyright (c) 2020 - present Cloudogu GmbH
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify it under
|
||||
* the terms of the GNU Affero General Public License as published by the Free
|
||||
* Software Foundation, version 3.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
|
||||
* FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more
|
||||
* details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see https://www.gnu.org/licenses/.
|
||||
*/
|
||||
|
||||
package sonia.scm.store;
|
||||
|
||||
import lombok.Getter;
|
||||
import lombok.Value;
|
||||
|
||||
@Value
|
||||
@Getter
|
||||
public class LogicalCondition<T> implements Condition<T> {
|
||||
LogicalOperator operator;
|
||||
Condition<T>[] conditions;
|
||||
|
||||
LogicalCondition(LogicalOperator operator, Condition<T>[] conditions) {
|
||||
this.operator = operator;
|
||||
this.conditions = conditions;
|
||||
}
|
||||
}
|
||||
21
scm-core/src/main/java/sonia/scm/store/LogicalOperator.java
Normal file
21
scm-core/src/main/java/sonia/scm/store/LogicalOperator.java
Normal file
@@ -0,0 +1,21 @@
|
||||
/*
|
||||
* Copyright (c) 2020 - present Cloudogu GmbH
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify it under
|
||||
* the terms of the GNU Affero General Public License as published by the Free
|
||||
* Software Foundation, version 3.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
|
||||
* FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more
|
||||
* details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see https://www.gnu.org/licenses/.
|
||||
*/
|
||||
|
||||
package sonia.scm.store;
|
||||
|
||||
public enum LogicalOperator {
|
||||
AND, OR, NOT;
|
||||
}
|
||||
32
scm-core/src/main/java/sonia/scm/store/Operator.java
Normal file
32
scm-core/src/main/java/sonia/scm/store/Operator.java
Normal file
@@ -0,0 +1,32 @@
|
||||
/*
|
||||
* Copyright (c) 2020 - present Cloudogu GmbH
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify it under
|
||||
* the terms of the GNU Affero General Public License as published by the Free
|
||||
* Software Foundation, version 3.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
|
||||
* FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more
|
||||
* details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see https://www.gnu.org/licenses/.
|
||||
*/
|
||||
|
||||
package sonia.scm.store;
|
||||
|
||||
public enum Operator {
|
||||
|
||||
EQ,
|
||||
LESS,
|
||||
GREATER,
|
||||
LESS_OR_EQUAL,
|
||||
GREATER_OR_EQUAL,
|
||||
CONTAINS,
|
||||
IN,
|
||||
NULL,
|
||||
KEY,
|
||||
VALUE
|
||||
|
||||
}
|
||||
@@ -0,0 +1,146 @@
|
||||
/*
|
||||
* Copyright (c) 2020 - present Cloudogu GmbH
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify it under
|
||||
* the terms of the GNU Affero General Public License as published by the Free
|
||||
* Software Foundation, version 3.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
|
||||
* FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more
|
||||
* details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see https://www.gnu.org/licenses/.
|
||||
*/
|
||||
|
||||
package sonia.scm.store;
|
||||
|
||||
import com.google.common.collect.Streams;
|
||||
import jakarta.xml.bind.annotation.XmlAccessType;
|
||||
import jakarta.xml.bind.annotation.XmlAccessorType;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.Iterator;
|
||||
import java.util.Optional;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
/**
|
||||
* This store should be used only in update steps or other maintenance tasks like deleting all entries for a deleted
|
||||
* parent entity.
|
||||
*
|
||||
* @param <T> The entity type of the store.
|
||||
*/
|
||||
public interface QueryableMaintenanceStore<T> {
|
||||
|
||||
Collection<Row<T>> readAll() throws SerializationException;
|
||||
|
||||
<U> Collection<Row<U>> readAllAs(Class<U> type) throws SerializationException;
|
||||
|
||||
Collection<RawRow> readRaw();
|
||||
|
||||
@SuppressWarnings("rawtypes")
|
||||
default void writeAll(Iterable<Row> rows) throws SerializationException {
|
||||
writeAll(Streams.stream(rows));
|
||||
}
|
||||
|
||||
@SuppressWarnings("rawtypes")
|
||||
void writeAll(Stream<Row> rows) throws SerializationException;
|
||||
|
||||
default void writeRaw(Iterable<RawRow> rows) {
|
||||
writeRaw(Streams.stream(rows));
|
||||
}
|
||||
|
||||
void writeRaw(Stream<RawRow> rows);
|
||||
|
||||
@Data
|
||||
@XmlAccessorType(XmlAccessType.FIELD)
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
class Row<U> {
|
||||
private String[] parentIds;
|
||||
private String id;
|
||||
private U value;
|
||||
}
|
||||
|
||||
@Data
|
||||
@XmlAccessorType(XmlAccessType.FIELD)
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
class RawRow {
|
||||
private String[] parentIds;
|
||||
private String id;
|
||||
private String value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Deletes all entries from the store. If the store has been created limited to a concrete parent
|
||||
* or a subset of parents, only the entries for this parent(s) will be deleted.
|
||||
*/
|
||||
void clear();
|
||||
|
||||
/**
|
||||
* Returns an iterator to iterate over all entries in the store. If the store has been created limited to a concrete parent
|
||||
* or a subset of parents, only the entries for this parent(s) will be returned.
|
||||
* The iterated values offer additional methods to update or delete entries.
|
||||
* <br>
|
||||
* The iterator must be closed after usage. Otherwise, updates may not be persisted.
|
||||
*/
|
||||
MaintenanceIterator<T> iterateAll();
|
||||
|
||||
/**
|
||||
* Iterator for existing entries in the store.
|
||||
*/
|
||||
interface MaintenanceIterator<T> extends Iterator<MaintenanceStoreEntry<T>>, AutoCloseable {
|
||||
}
|
||||
|
||||
/**
|
||||
* Maintenance helper for a concrete entry in the store.
|
||||
*/
|
||||
interface MaintenanceStoreEntry<T> {
|
||||
|
||||
/**
|
||||
* The id of the entry.
|
||||
*/
|
||||
String getId();
|
||||
|
||||
/**
|
||||
* Returns the id of the parent for the given class.
|
||||
*/
|
||||
Optional<String> getParentId(Class<?> clazz);
|
||||
|
||||
/**
|
||||
* Returns the entity as the specified type of the store.
|
||||
*
|
||||
* @throws SerializationException if the entry cannot be deserialized to the type of the store.
|
||||
*/
|
||||
T get();
|
||||
|
||||
/**
|
||||
* Returns the entry as the given type, not as the type that has been specified for the store.
|
||||
* This can be used whenever the type of the store has been changed in a way that no longer is compatible with the
|
||||
* stored data. In this case, the entry can be deserialized to a different type that is only used during the
|
||||
* migration.
|
||||
*
|
||||
* @param <U> The type of the entry.
|
||||
* @throws SerializationException if the entry cannot be deserialized to the given type.
|
||||
*/
|
||||
<U> U getAs(Class<U> type);
|
||||
|
||||
/**
|
||||
* Update the store entry with the given object.
|
||||
*
|
||||
* @throws SerializationException if the object cannot be serialized.
|
||||
*/
|
||||
void update(Object object);
|
||||
}
|
||||
|
||||
class SerializationException extends RuntimeException {
|
||||
public SerializationException(String message, Throwable cause) {
|
||||
super(message, cause);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
/*
|
||||
* Copyright (c) 2020 - present Cloudogu GmbH
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify it under
|
||||
* the terms of the GNU Affero General Public License as published by the Free
|
||||
* Software Foundation, version 3.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
|
||||
* FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more
|
||||
* details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see https://www.gnu.org/licenses/.
|
||||
*/
|
||||
|
||||
package sonia.scm.store;
|
||||
|
||||
import java.util.function.BooleanSupplier;
|
||||
|
||||
/**
|
||||
* This interface is used to store objects annotated with {@link QueryableType}.
|
||||
* It combines the functionality of a {@link DataStore} and a {@link QueryableStore}.
|
||||
* In contrast to the {@link QueryableStore}, instances are always scoped to a specific parent (if the type this store
|
||||
* is created for as parent types specified in its annotation).
|
||||
* It will be created by the {@link QueryableStoreFactory}.
|
||||
* <br/>
|
||||
* It is not meant to be instantiated by users of the API. Instead, use the query factory created by the annotation
|
||||
* processor for the annotated type.
|
||||
*
|
||||
* @param <T> The type of the objects to query.
|
||||
* @since 3.7.0
|
||||
*/
|
||||
public interface QueryableMutableStore<T> extends DataStore<T>, QueryableStore<T>, AutoCloseable {
|
||||
void transactional(BooleanSupplier callback);
|
||||
|
||||
@Override
|
||||
void close();
|
||||
}
|
||||
668
scm-core/src/main/java/sonia/scm/store/QueryableStore.java
Normal file
668
scm-core/src/main/java/sonia/scm/store/QueryableStore.java
Normal file
@@ -0,0 +1,668 @@
|
||||
/*
|
||||
* Copyright (c) 2020 - present Cloudogu GmbH
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify it under
|
||||
* the terms of the GNU Affero General Public License as published by the Free
|
||||
* Software Foundation, version 3.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
|
||||
* FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more
|
||||
* details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see https://www.gnu.org/licenses/.
|
||||
*/
|
||||
|
||||
package sonia.scm.store;
|
||||
|
||||
import java.time.Instant;
|
||||
import java.time.temporal.ChronoUnit;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
|
||||
/**
|
||||
* This interface is used to query objects annotated with {@link QueryableType}. It will be created by the
|
||||
* {@link QueryableStoreFactory}.
|
||||
* <br/>
|
||||
* It is not meant to be instantiated by users of the API. Instead, use the query factory created by the annotation
|
||||
* processor for the annotated type.
|
||||
*
|
||||
* @param <T> The type of the objects to query.
|
||||
* @since 3.7.0
|
||||
*/
|
||||
public interface QueryableStore<T> extends AutoCloseable {
|
||||
|
||||
/**
|
||||
* Creates a query for the objects of the type {@code T} with the given conditions. Conditions should be created by
|
||||
* either using the static methods of the {@link Conditions} class or by using the query fields of the type {@code T}
|
||||
* that will be created by the annotation processor in a separate class. If your annotated type is named
|
||||
* {@code MyType}, the query fields class will be named {@code MyTypeQueryFields}.
|
||||
* <br/>
|
||||
* If no conditions are given, all objects of the type {@code T} will be returned (limited by the ids of the
|
||||
* parent objects that had been specified when this instance of the store had been created by the factory). If more
|
||||
* than one condition is given, the conditions will be combined with a logical AND.
|
||||
*
|
||||
* @param conditions The conditions to filter the objects.
|
||||
* @return The query object to retrieve the result.
|
||||
*/
|
||||
Query<T, T> query(Condition<T>... conditions);
|
||||
|
||||
/**
|
||||
* Used to specify the order of the result of a query.
|
||||
*/
|
||||
enum Order {
|
||||
/**
|
||||
* Ascending order.
|
||||
*/
|
||||
ASC,
|
||||
/**
|
||||
* Descending order.
|
||||
*/
|
||||
DESC
|
||||
}
|
||||
|
||||
/**
|
||||
* The terminal interface for a query build by {@link #query(Condition[])}. It provides methods to retrieve the
|
||||
* result of the query in different forms.
|
||||
*
|
||||
* @param <T> The type of the objects to query.
|
||||
* @param <T_RESULT> The type of the result objects (if a projection had been made, for example using
|
||||
* {@link #withIds()}).
|
||||
*/
|
||||
interface Query<T, T_RESULT> {
|
||||
|
||||
/**
|
||||
* Returns the first found object, if the query returns at least one result.
|
||||
* If the query returns no result, an empty optional will be returned.
|
||||
*/
|
||||
Optional<T_RESULT> findFirst();
|
||||
|
||||
/**
|
||||
* Returns the found object, if the query returns one exactly one result. When the query returns more than one
|
||||
* result, a {@link TooManyResultsException} will be thrown. If the query returns no result, an empty optional will be returned.
|
||||
*/
|
||||
Optional<T_RESULT> findOne() throws TooManyResultsException;
|
||||
|
||||
/**
|
||||
* Returns all objects that match the query. If the query returns no result, an empty list will be returned.
|
||||
*/
|
||||
List<T_RESULT> findAll();
|
||||
|
||||
/**
|
||||
* Returns a subset of all objects that match the query. If the query returns no result or the {@code offset} and
|
||||
* {@code limit} are set in a way, that the result is exceeded, an empty list will be returned.
|
||||
*
|
||||
* @param offset The offset to start the result list.
|
||||
* @param limit The maximum number of results to return.
|
||||
*/
|
||||
List<T_RESULT> findAll(long offset, long limit);
|
||||
|
||||
/**
|
||||
* Returns the found objects in combination with the parent ids they belong to. This is useful if you are using a
|
||||
* queryable store that is not scoped to specific parent objects, and you therefore want to know to which parent
|
||||
* objects each of the found objects belong to.
|
||||
*
|
||||
* @return The query object to continue building the query.
|
||||
*/
|
||||
Query<T, Result<T_RESULT>> withIds();
|
||||
|
||||
/**
|
||||
* Orders the result by the given field in the given order. If the order is not set, the order of the result is not
|
||||
* specified. Orders can be chained, so you can call this method multiple times to order by multiple fields.
|
||||
*
|
||||
* @param field The field to order by.
|
||||
* @param order The order to use (either ascending or descending).
|
||||
* @return The query object to continue building the query.
|
||||
*/
|
||||
Query<T, T_RESULT> orderBy(QueryField<T, ?> field, Order order);
|
||||
|
||||
/**
|
||||
* Returns the count of all objects that match the query.
|
||||
*/
|
||||
long count();
|
||||
}
|
||||
|
||||
/**
|
||||
* The result of a query that was built by {@link QueryableStore.Query#withIds()}. It contains the parent ids of the
|
||||
* found objects in addition to the objects and their ids themselves.
|
||||
*
|
||||
* @param <T> The type of the queried objects.
|
||||
*/
|
||||
interface Result<T> {
|
||||
/**
|
||||
* Returns the parent ids of the found objects. The parent ids are ordered in the same way as their types are
|
||||
* specified in the @{@link QueryableType} annotation for the queried type.
|
||||
*/
|
||||
Optional<String> getParentId(Class<?> clazz);
|
||||
|
||||
/**
|
||||
* Returns the id of the found object.
|
||||
*/
|
||||
String getId();
|
||||
|
||||
/**
|
||||
* Returns the found object itself.
|
||||
*/
|
||||
T getEntity();
|
||||
}
|
||||
|
||||
/**
|
||||
* Instances of this class will be created by the annotation processor for each class annotated with
|
||||
* {@link QueryableType}. It provides query fields for the annotated class to build queries with.
|
||||
* <br/>
|
||||
* This is not meant to be extended or instantiated by users of the API!
|
||||
*
|
||||
* @param <T> The type of the objects this field is used for.
|
||||
* @param <F> The type of the field.
|
||||
*/
|
||||
@SuppressWarnings("unused")
|
||||
class QueryField<T, F> {
|
||||
final String name;
|
||||
|
||||
public QueryField(String name) {
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
public boolean isIdField() {
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a condition that checks if the field is null.
|
||||
*
|
||||
* @return The condition to use in a query.
|
||||
*/
|
||||
public Condition<T> isNull() {
|
||||
return new LeafCondition<>(this, Operator.NULL, null);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* This class is used to create conditions for queries. Instances of this class will be created by the annotation
|
||||
* processor for each {@link String} field of a class annotated with {@link QueryableType}.
|
||||
* <br/>
|
||||
* This is not meant to be instantiated by users of the API!
|
||||
*
|
||||
* @param <T> The type of the objects this condition is used for.
|
||||
*/
|
||||
class StringQueryField<T> extends QueryField<T, String> {
|
||||
|
||||
public StringQueryField(String name) {
|
||||
super(name);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a condition that checks if the field is equal to the given value.
|
||||
*
|
||||
* @param value The value to compare the field with.
|
||||
* @return The condition to use in a query.
|
||||
*/
|
||||
public LeafCondition<T, String> eq(String value) {
|
||||
return new LeafCondition<>(this, Operator.EQ, value);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a condition that checks if the field contains the given value as a substring.
|
||||
*
|
||||
* @param value The value to check for.
|
||||
* @return The condition to use in a query.
|
||||
*/
|
||||
public Condition<T> contains(String value) {
|
||||
return new LeafCondition<>(this, Operator.CONTAINS, value);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Creates a condition that checks if the field is equal to any of the given values.
|
||||
*
|
||||
* @param values The values to compare the field with.
|
||||
* @return The condition to use in a query.
|
||||
*/
|
||||
public Condition<T> in(String... values) {
|
||||
return new LeafCondition<>(this, Operator.IN, values);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a condition that checks if the field is equal to any of the given values.
|
||||
*
|
||||
* @param values The values to compare the field with.
|
||||
* @return The condition to use in a query.
|
||||
*/
|
||||
public Condition<T> in(Collection<String> values) {
|
||||
return in(values.toArray(new String[0]));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* This class is used to create conditions for queries. Instances of this class will be created by the annotation
|
||||
* processor for a class annotated with {@link QueryableType}.
|
||||
* <br/>
|
||||
* This is not meant to be instantiated by users of the API!
|
||||
*
|
||||
* @param <T> The type of the objects this condition is used for.
|
||||
*/
|
||||
class IdQueryField<T> extends StringQueryField<T> {
|
||||
public IdQueryField(Class<?> clazz) {
|
||||
super(clazz.getName());
|
||||
}
|
||||
|
||||
public IdQueryField() {
|
||||
super(null);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isIdField() {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* This class is used to create conditions for queries. Instances of this class will be created by the annotation
|
||||
* processor for each number field (either {@link Integer}, {@link Long}, {@code int}, or {@code long}) of a class
|
||||
* annotated with {@link QueryableType}.
|
||||
* <br/>
|
||||
* This is not meant to be instantiated by users of the API!
|
||||
*
|
||||
* @param <T> The type of the objects this condition is used for.
|
||||
* @param <N> The type of the number field.
|
||||
*/
|
||||
class NumberQueryField<T, N extends Number> extends QueryField<T, N> {
|
||||
|
||||
public NumberQueryField(String name) {
|
||||
super(name);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a condition that checks if the field is equal to the given value.
|
||||
*
|
||||
* @param value The value to compare the field with.
|
||||
* @return The condition to use in a query.
|
||||
*/
|
||||
public Condition<T> eq(N value) {
|
||||
return new LeafCondition<>(this, Operator.EQ, value);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a condition that checks if the field is greater than the given value.
|
||||
*
|
||||
* @param value The value to compare the field with.
|
||||
* @return The condition to use in a query.
|
||||
*/
|
||||
public Condition<T> greater(N value) {
|
||||
return new LeafCondition<>(this, Operator.GREATER, value);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a condition that checks if the field is less than the given value.
|
||||
*
|
||||
* @param value The value to compare the field with.
|
||||
* @return The condition to use in a query.
|
||||
*/
|
||||
public Condition<T> less(N value) {
|
||||
return new LeafCondition<>(this, Operator.LESS, value);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a condition that checks if the field is greater than or equal to the given value.
|
||||
*
|
||||
* @param value The value to compare the field with.
|
||||
* @return The condition to use in a query.
|
||||
*/
|
||||
public Condition<T> greaterOrEquals(N value) {
|
||||
return new LeafCondition<>(this, Operator.GREATER_OR_EQUAL, value);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a condition that checks if the field is less than or equal to the given value.
|
||||
*
|
||||
* @param value The value to compare the field with.
|
||||
* @return The condition to use in a query.
|
||||
*/
|
||||
public Condition<T> lessOrEquals(N value) {
|
||||
return new LeafCondition<>(this, Operator.LESS_OR_EQUAL, value);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a condition that checks if the fields is inclusively between the from and to values.
|
||||
*
|
||||
* @param from The lower limit to compare the value with.
|
||||
* @param to The upper limit to compare the value with.
|
||||
* @return The condition to use in a query.
|
||||
*/
|
||||
public Condition<T> between(N from, N to) {
|
||||
return Conditions.and(lessOrEquals(to), greaterOrEquals(from));
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a condition that checks if the field is equal to any of the given values.
|
||||
*
|
||||
* @param values The values to compare the field with.
|
||||
* @return The condition to use in a query.
|
||||
*/
|
||||
public Condition<T> in(N... values) {
|
||||
return new LeafCondition<>(this, Operator.IN, values);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a condition that checks if the field is equal to any of the given values.
|
||||
*
|
||||
* @param values The values to compare the field with.
|
||||
* @return The condition to use in a query.
|
||||
*/
|
||||
public Condition<T> in(Collection<N> values) {
|
||||
return new LeafCondition<>(this, Operator.IN, values.toArray(new Object[0]));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* This class is used to create conditions for queries. Instances of this class will be created by the annotation
|
||||
* processor for each date field of a class annotated with {@link QueryableType}.
|
||||
* <br/>
|
||||
* This is not meant to be instantiated by users of the API!
|
||||
*
|
||||
* @param <T> The type of the objects this condition is used for.
|
||||
*/
|
||||
class InstantQueryField<T> extends QueryField<T, Instant> {
|
||||
public InstantQueryField(String name) {
|
||||
super(name);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a condition that checks if the field is equal to the given value. The given instant will be truncated to
|
||||
* milliseconds.
|
||||
*
|
||||
* @param value The value to compare the field with.
|
||||
* @return The condition to use in a query.
|
||||
*/
|
||||
public Condition<T> eq(Instant value) {
|
||||
return new LeafCondition<>(this, Operator.EQ, value.truncatedTo(ChronoUnit.MILLIS));
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a condition that checks if the field is after the given value.
|
||||
*
|
||||
* @param value The value to compare the field with.
|
||||
* @return The condition to use in a query.
|
||||
*/
|
||||
public Condition<T> after(Instant value) {
|
||||
return new LeafCondition<>(this, Operator.GREATER, value);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a condition that checks if the field is before the given value.
|
||||
*
|
||||
* @param value The value to compare the field with.
|
||||
* @return The condition to use in a query.
|
||||
*/
|
||||
public Condition<T> before(Instant value) {
|
||||
return new LeafCondition<>(this, Operator.LESS, value);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a condition that checks if the field is between the given values.
|
||||
*
|
||||
* @param from The lower bound of the range to compare the field with.
|
||||
* @param to The upper bound of the range to compare the field with.
|
||||
* @return The condition to use in a query.
|
||||
*/
|
||||
public Condition<T> between(Instant from, Instant to) {
|
||||
return Conditions.and(after(from), before(to));
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a condition that checks if the field is equal to the given value.
|
||||
*
|
||||
* @param value The value to compare the field with.
|
||||
* @return The condition to use in a query.
|
||||
*/
|
||||
public Condition<T> eq(Date value) {
|
||||
return eq(value.toInstant());
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a condition that checks if the field is after the given value.
|
||||
*
|
||||
* @param value The value to compare the field with.
|
||||
* @return The condition to use in a query.
|
||||
*/
|
||||
public Condition<T> after(Date value) {
|
||||
return after(value.toInstant());
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a condition that checks if the field is before the given value.
|
||||
*
|
||||
* @param value The value to compare the field with.
|
||||
* @return The condition to use in a query.
|
||||
*/
|
||||
public Condition<T> before(Date value) {
|
||||
return before(value.toInstant());
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a condition that checks if the field is between the given values.
|
||||
*
|
||||
* @param from The lower bound of the range to compare the field with.
|
||||
* @param to The upper bound of the range to compare the field with.
|
||||
* @return The condition to use in a query.
|
||||
*/
|
||||
public Condition<T> between(Date from, Date to) {
|
||||
return between(from.toInstant(), to.toInstant());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* This class is used to create conditions for queries. Instances of this class will be created by the annotation
|
||||
* processor for each boolean field of a class annotated with {@link QueryableType}.
|
||||
* <br/>
|
||||
* This is not meant to be instantiated by users of the API!
|
||||
*
|
||||
* @param <T> The type of the objects this condition is used for.
|
||||
*/
|
||||
class BooleanQueryField<T> extends QueryField<T, Boolean> {
|
||||
|
||||
public BooleanQueryField(String name) {
|
||||
super(name);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a condition that checks if the field is equal to the given value.
|
||||
*
|
||||
* @param b The value to compare the field with.
|
||||
* @return The condition to use in a query.
|
||||
*/
|
||||
public Condition<T> eq(Boolean b) {
|
||||
return new LeafCondition<>(this, Operator.EQ, b);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a condition that checks if the field is true.
|
||||
*
|
||||
* @return The condition to use in a query.
|
||||
*/
|
||||
public Condition<T> isTrue() {
|
||||
return eq(Boolean.TRUE);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a condition that checks if the field is false.
|
||||
*
|
||||
* @return The condition to use in a query.
|
||||
*/
|
||||
public Condition<T> isFalse() {
|
||||
return eq(Boolean.FALSE);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* This class is used to create conditions for queries. Instances of this class will be created by the annotation
|
||||
* processor for each enum field of a class annotated with {@link QueryableType}.
|
||||
* <br/>
|
||||
* This is not meant to be instantiated by users of the API!
|
||||
*
|
||||
* @param <T> The type of the objects this condition is used for.
|
||||
* @param <E> The type of the enum field.
|
||||
*/
|
||||
class EnumQueryField<T, E extends Enum<E>> extends QueryField<T, Enum<E>> {
|
||||
public EnumQueryField(String name) {
|
||||
super(name);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a condition that checks if the field is equal to the given value.
|
||||
*
|
||||
* @param value The value to compare the field with.
|
||||
* @return The condition to use in a query.
|
||||
*/
|
||||
public LeafCondition<T, String> eq(E value) {
|
||||
return new LeafCondition<>(this, Operator.EQ, value.name());
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a condition that checks if the field is equal to any of the given values.
|
||||
*
|
||||
* @param values The values to compare the field with.
|
||||
* @return The condition to use in a query.
|
||||
*/
|
||||
public Condition<T> in(E... values) {
|
||||
return new LeafCondition<>(this, Operator.IN, Arrays.stream(values).map(Enum::name).toArray());
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a condition that checks if the field is equal to any of the given values.
|
||||
*
|
||||
* @param values The values to compare the field with.
|
||||
* @return The condition to use in a query.
|
||||
*/
|
||||
public Condition<T> in(Collection<E> values) {
|
||||
return new LeafCondition<>(this, Operator.IN, values.stream().map(Enum::name).toArray());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* This class is used to create conditions for queries. Instances of this class will be created by the annotation
|
||||
* processor for each collection field of a class annotated with {@link QueryableType}. Note that this can only be
|
||||
* used for collections of base types like {@link String}, number types, enums or booleans.
|
||||
* <br/>
|
||||
* This is not meant to be instantiated by users of the API!
|
||||
*
|
||||
* @param <T> The type of the objects this condition is used for.
|
||||
*/
|
||||
class CollectionQueryField<T> extends QueryField<T, Object> {
|
||||
public CollectionQueryField(String name) {
|
||||
super(name);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a condition that checks if the field contains the given value.
|
||||
*
|
||||
* @param value The value to check for.
|
||||
* @return The condition to use in a query.
|
||||
*/
|
||||
public Condition<T> contains(Object value) {
|
||||
return new LeafCondition<>(this, Operator.EQ, value);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* This class is used to create conditions for queries, based on the size of a collection.
|
||||
* Instances of this class will be created by the annotation processor for each collection
|
||||
* field of a class annotated with {@link QueryableType}. Note that this can only be used
|
||||
* for collections of base types like {@link String}, number types, enums or booleans.
|
||||
* <br/>
|
||||
* This is not meant to be instantiated by users of the API!
|
||||
*
|
||||
* @param <T> The type of the objects this condition is used for.
|
||||
*/
|
||||
class CollectionSizeQueryField<T> extends NumberQueryField<T, Long> {
|
||||
public CollectionSizeQueryField(String name) {
|
||||
super(name);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a condition that checks if the collection field is empty.
|
||||
*
|
||||
* @return The condition to use in a query.
|
||||
*/
|
||||
public Condition<T> isEmpty() {
|
||||
return eq(0L);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* This class is used to create conditions for queries. Instances of this class will be created by the annotation
|
||||
* processor for each map field of a class annotated with {@link QueryableType}. Note that this can only be used for
|
||||
* maps with base types like {@link String}, number types, enums or booleans as keys.
|
||||
* <br/>
|
||||
* This is not meant to be instantiated by users of the API!
|
||||
*
|
||||
* @param <T> The type of the objects this condition is used for.
|
||||
*/
|
||||
class MapQueryField<T> extends QueryField<T, Object> {
|
||||
public MapQueryField(String name) {
|
||||
super(name);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a condition that checks if the field contains the given key.
|
||||
*
|
||||
* @param key The key to check for.
|
||||
* @return The condition to use in a query.
|
||||
*/
|
||||
public Condition<T> containsKey(Object key) {
|
||||
return new LeafCondition<>(this, Operator.KEY, key);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a condition that checks if the field contains the given value.
|
||||
*
|
||||
* @param value The value to check for.
|
||||
* @return The condition to use in a query.
|
||||
*/
|
||||
public Condition<T> containsValue(Object value) {
|
||||
return new LeafCondition<>(this, Operator.VALUE, value);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* This class is used to create conditions for queries, based on the size of a map.
|
||||
* Instances of this class will be created by the annotation processor for each map
|
||||
* field of a class annotated with {@link QueryableType}. Note that this can only be used
|
||||
* for collections of base types like {@link String}, number types, enums or booleans.
|
||||
* <br/>
|
||||
* This is not meant to be instantiated by users of the API!
|
||||
*
|
||||
* @param <T> The type of the objects this condition is used for.
|
||||
*/
|
||||
class MapSizeQueryField<T> extends NumberQueryField<T, Long> {
|
||||
public MapSizeQueryField(String name) {
|
||||
super(name);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a condition that checks if the map field is empty.
|
||||
*
|
||||
* @return The condition to use in a query.
|
||||
*/
|
||||
public Condition<T> isEmpty() {
|
||||
return eq(0L);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* An exception occurring, if the client queried for one result with {@link Query#findOne()}, but the query returned multiple results.
|
||||
*/
|
||||
class TooManyResultsException extends RuntimeException {
|
||||
public TooManyResultsException() {
|
||||
super("Found more than one result");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,66 @@
|
||||
/*
|
||||
* Copyright (c) 2020 - present Cloudogu GmbH
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify it under
|
||||
* the terms of the GNU Affero General Public License as published by the Free
|
||||
* Software Foundation, version 3.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
|
||||
* FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more
|
||||
* details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see https://www.gnu.org/licenses/.
|
||||
*/
|
||||
|
||||
package sonia.scm.store;
|
||||
|
||||
/**
|
||||
* Factory to create {@link QueryableStore} and {@link QueryableMutableStore} instances.
|
||||
* In comparison to the {@link DataStoreFactory}, this factory is used to create stores which can execute
|
||||
* queries on the stored data. Queryable stores can be used for types which are annotated with {@link QueryableType}.
|
||||
* <br/>
|
||||
* Normally, there should be no need to use this factory directly. Instead, for each type annotated with
|
||||
* {@link QueryableType} a dedicated store factory is generated which can be injected into other components.
|
||||
* For instance, if your data class is named {@code MyData} and annotated with {@link QueryableType}, a factory
|
||||
* you should find a {@code MyDataStoreFactory} for your needs which is backed by this class.
|
||||
* <br/>
|
||||
* Implementations probably are backed by a database or a similar storage system instead of the familiar
|
||||
* file based storage using XML.
|
||||
*
|
||||
* @since 3.7.0
|
||||
*/
|
||||
public interface QueryableStoreFactory {
|
||||
|
||||
/**
|
||||
* Creates a read-only store for the given class and optional parent ids. If parent ids are omitted, queries
|
||||
* will not be restricted to a specific parent (for example a repository) but will run on all data of the given type.
|
||||
*
|
||||
* @param clazz The class of the data type (must be annotated with {@link QueryableType}).
|
||||
* @param parentIds Optional parent ids to restrict the query to a specific parent.
|
||||
* @param <T> The type of the data.
|
||||
* @return A read-only store for the given class and optional parent ids.
|
||||
*/
|
||||
<T> QueryableStore<T> getReadOnly(Class<T> clazz, String... parentIds);
|
||||
|
||||
/**
|
||||
* Creates a mutable store for the given class and parent ids. In contrast to the read-only store, for a mutable store
|
||||
* the parent ids are mandatory. For each parent class given in the {@link QueryableType} annotation of the type, a
|
||||
* concrete id has to be specified. This is because mutable stores are used to store data, which is done for the
|
||||
* concrete parents only. So if data should be stored for different parents, separate mutable stores have to be
|
||||
* created.
|
||||
* <br/>
|
||||
* The mutable store provides methods to store, update and delete data but also all query methods of the read-only
|
||||
* store.
|
||||
*
|
||||
* @param clazz The class of the data type (must be annotated with {@link QueryableType}).
|
||||
* @param parentIds Ids for all parent classes named in the {@link QueryableType} annotation.
|
||||
* @param <T> The type of the data.
|
||||
* @return A mutable store for the given class scoped to the given parents.
|
||||
*/
|
||||
<T> QueryableMutableStore<T> getMutable(Class<T> clazz, String... parentIds);
|
||||
|
||||
<T> QueryableMaintenanceStore<T> getForMaintenance(Class<T> clazz, String... parentIds);
|
||||
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
/*
|
||||
* Copyright (c) 2020 - present Cloudogu GmbH
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify it under
|
||||
* the terms of the GNU Affero General Public License as published by the Free
|
||||
* Software Foundation, version 3.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
|
||||
* FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more
|
||||
* details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see https://www.gnu.org/licenses/.
|
||||
*/
|
||||
|
||||
package sonia.scm.store;
|
||||
|
||||
import sonia.scm.plugin.ExtensionPoint;
|
||||
|
||||
@ExtensionPoint
|
||||
public interface StoreDeletionNotifier {
|
||||
void registerHandler(DeletionHandler handler);
|
||||
|
||||
interface DeletionHandler {
|
||||
default void notifyDeleted(Class<?> clazz, String id) {
|
||||
notifyDeleted(new ClassWithId(clazz, id));
|
||||
}
|
||||
void notifyDeleted(ClassWithId... classWithIds);
|
||||
}
|
||||
|
||||
record ClassWithId(Class<?> clazz, String id) {}
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
/*
|
||||
* Copyright (c) 2020 - present Cloudogu GmbH
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify it under
|
||||
* the terms of the GNU Affero General Public License as published by the Free
|
||||
* Software Foundation, version 3.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
|
||||
* FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more
|
||||
* details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see https://www.gnu.org/licenses/.
|
||||
*/
|
||||
|
||||
package sonia.scm.store;
|
||||
|
||||
import java.util.Collection;
|
||||
|
||||
public interface StoreMetaDataProvider {
|
||||
Collection<Class<?>> getTypesWithParent(Class<?>... classes);
|
||||
}
|
||||
@@ -16,6 +16,7 @@
|
||||
|
||||
package sonia.scm.update;
|
||||
|
||||
import sonia.scm.store.QueryableMaintenanceStore;
|
||||
import sonia.scm.store.StoreParameters;
|
||||
import sonia.scm.store.StoreType;
|
||||
|
||||
@@ -25,6 +26,8 @@ public interface StoreUpdateStepUtilFactory {
|
||||
return new UtilForTypeBuilder(this, type);
|
||||
}
|
||||
|
||||
<T> QueryableMaintenanceStore<T> forQueryableType(Class<T> clazz, String... parents);
|
||||
|
||||
final class UtilForTypeBuilder {
|
||||
private final StoreUpdateStepUtilFactory factory;
|
||||
private final StoreType type;
|
||||
|
||||
Reference in New Issue
Block a user