diff --git a/cloud-pipeline b/cloud-pipeline deleted file mode 160000 index b84f41bd..00000000 --- a/cloud-pipeline +++ /dev/null @@ -1 +0,0 @@ -Subproject commit b84f41bd9d305d84e564c4bdb7956169036373f3 diff --git a/cloud-services/pom.xml b/cloud-services/pom.xml new file mode 100644 index 00000000..cdb96477 --- /dev/null +++ b/cloud-services/pom.xml @@ -0,0 +1,126 @@ + + + 4.0.0 + + + cloud + cloud.commandframework + 1.0-SNAPSHOT + + + cloud-services + 1.5.0-SNAPSHOT + jar + 2020 + + + MIT License + https://raw.githubusercontent.com/Sauilitired/Rörledning/master/LICENSE + + + + Services for Java + + 1.8 + UTF-8 + + + + intellectualsites + IntellectualSites + https://mvn.intellectualsites.com/content/repositories/releases + + + intellectualsites-snapshots + IntellectualSites Snapshots + https://mvn.intellectualsites.com/content/repositories/snapshots + + + + clean package + + + src/main/resources + true + + + + + org.apache.maven.plugins + maven-compiler-plugin + 3.8.1 + + ${java.version} + ${java.version} + + + + org.apache.maven.plugins + maven-deploy-plugin + 2.8.2 + + true + + + + org.sonatype.plugins + nexus-staging-maven-plugin + 1.6.8 + + + default-deploy + deploy + + deploy + + + + + intellectualsites + https://mvn.intellectualsites.com/ + true + + + + org.apache.maven.plugins + maven-javadoc-plugin + 3.2.0 + + + attach-javadocs + + jar + + + + + + org.apache.maven.plugins + maven-source-plugin + 3.2.1 + + + attach-sources + + jar + + + + + + org.apache.maven.plugins + maven-surefire-plugin + 2.22.2 + + + + + + com.google.guava + guava + 29.0-jre + + + diff --git a/cloud-services/src/main/java/cloud/commandframework/services/ChunkedRequestContext.java b/cloud-services/src/main/java/cloud/commandframework/services/ChunkedRequestContext.java new file mode 100644 index 00000000..eafd6512 --- /dev/null +++ b/cloud-services/src/main/java/cloud/commandframework/services/ChunkedRequestContext.java @@ -0,0 +1,105 @@ +// +// MIT License +// +// Copyright (c) 2020 IntellectualSites +// +// 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 cloud.commandframework.services; + +import javax.annotation.Nonnull; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +/** + * This class represents a request that can be fulfilled by one, or multiple services, for one or + * more objects + * + * @param Context/Request type + * @param Result type + */ +public abstract class ChunkedRequestContext { + + private final Object lock = new Object(); + private final List requests; + private final Map results; + + /** + * Initialize a new request + * + * @param requests Request contexts + */ + protected ChunkedRequestContext(@Nonnull final Collection requests) { + this.requests = new ArrayList<>(requests); + this.results = new HashMap<>(requests.size()); + } + + /** + * Get a view of the (currently) available results + * + * @return Unmodifiable map of results + */ + @Nonnull + public final Map getAvailableResults() { + synchronized (this.lock) { + return Collections.unmodifiableMap(this.results); + } + } + + /** + * Get all remaining requests + * + * @return Unmodifiable list of remaining requests + */ + @Nonnull + public final List getRemaining() { + synchronized (this.lock) { + return Collections.unmodifiableList(this.requests); + } + } + + /** + * Store a result for a specific context + * + * @param context Context + * @param result Result + */ + public final void storeResult(@Nonnull final Context context, @Nonnull final Result result) { + synchronized (this.lock) { + this.results.put(context, result); + this.requests.remove(context); + } + } + + /** + * Check if the request has been completed + * + * @return {@code true} if the request has been completed, {@code false} if not + */ + public final boolean isCompleted() { + synchronized (this.lock) { + return this.requests.isEmpty(); + } + } + +} diff --git a/cloud-services/src/main/java/cloud/commandframework/services/ServicePipeline.java b/cloud-services/src/main/java/cloud/commandframework/services/ServicePipeline.java new file mode 100644 index 00000000..95c5e853 --- /dev/null +++ b/cloud-services/src/main/java/cloud/commandframework/services/ServicePipeline.java @@ -0,0 +1,240 @@ +// +// MIT License +// +// Copyright (c) 2020 IntellectualSites +// +// 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 cloud.commandframework.services; + +import com.google.common.reflect.TypeToken; +import cloud.commandframework.services.types.Service; + +import javax.annotation.Nonnull; +import java.util.Collection; +import java.util.Collections; +import java.util.HashMap; +import java.util.LinkedList; +import java.util.List; +import java.util.Map; +import java.util.concurrent.Executor; +import java.util.function.Predicate; + +/** + * Service pipeline + */ +public final class ServicePipeline { + + private final Object lock = new Object(); + private final Map>, ServiceRepository> repositories; + private final Executor executor; + + ServicePipeline(@Nonnull final Executor executor) { + this.repositories = new HashMap<>(); + this.executor = executor; + } + + /** + * Create a new {@link ServicePipelineBuilder} + * + * @return Builder instance + */ + @Nonnull + public static ServicePipelineBuilder builder() { + return new ServicePipelineBuilder(); + } + + /** + * Register a service type so that it is recognized by the pipeline + * + * @param type Service type + * @param defaultImplementation Default implementation of the service. This must *always* be + * supplied and will be the last service implementation that the + * pipeline attempts to access. This will never be filtered out. + * @param Service context type + * @param Service result type + * @return ServicePipeline The service pipeline instance + */ + public ServicePipeline registerServiceType( + @Nonnull final TypeToken> type, + @Nonnull final Service defaultImplementation) { + synchronized (this.lock) { + if (repositories.containsKey(type)) { + throw new IllegalArgumentException(String + .format("Service of type '%s' has already been registered", type.toString())); + } + final ServiceRepository repository = new ServiceRepository<>(type); + repository.registerImplementation(defaultImplementation, Collections.emptyList()); + this.repositories.put(type, repository); + return this; + } + } + + /** + * Scan a given class for methods annotated with {@link cloud.commandframework.services.annotations.ServiceImplementation} + * and register them as service implementations. + *

+ * The methods should be of the form: + *

{@code
+     * {@literal @}Nullable
+     * {@literal @}ServiceImplementation(YourService.class)
+     * public YourResult handle(YourContext) {
+     *      return result;
+     * }
+     * }
+ * + * @param instance Instance of the class to scan + * @param Type of the instance + * @return Service pipeline instance + * @throws Exception Any exceptions thrown during the registration process + */ + @SuppressWarnings("unchecked") + public ServicePipeline registerMethods( + @Nonnull final T instance) throws Exception { + synchronized (this.lock) { + final Map, TypeToken>> services = + AnnotatedMethodServiceFactory.INSTANCE.lookupServices(instance); + for (final Map.Entry, TypeToken>> serviceEntry : services + .entrySet()) { + final TypeToken> type = serviceEntry.getValue(); + final ServiceRepository repository = this.repositories.get(type); + if (repository == null) { + throw new IllegalArgumentException( + String.format("No service registered for type '%s'", type.toString())); + } + repository.registerImplementation(serviceEntry.getKey(), + Collections.emptyList()); + } + } + return this; + } + + /** + * Register a service implementation for a type that is recognized by the pipeline. It is + * important that a call to {@link #registerServiceType(TypeToken, Service)} has been made + * beforehand, otherwise a {@link IllegalArgumentException} will be thrown + * + * @param type Service type + * @param implementation Implementation of the service + * @param filters Filters that will be used to determine whether or not the service gets + * used + * @param Service context type + * @param Service result type + * @return ServicePipeline The service pipeline instance + */ + public ServicePipeline registerServiceImplementation( + @Nonnull final TypeToken> type, + @Nonnull final Service implementation, + @Nonnull final Collection> filters) { + synchronized (this.lock) { + final ServiceRepository repository = getRepository(type); + repository.registerImplementation(implementation, filters); + } + return this; + } + + /** + * Register a service implementation for a type that is recognized by the pipeline. It is + * important that a call to {@link #registerServiceType(TypeToken, Service)} has been made + * beforehand, otherwise a {@link IllegalArgumentException} will be thrown + * + * @param type Service type + * @param implementation Implementation of the service + * @param filters Filters that will be used to determine whether or not the service gets + * used + * @param Service context type + * @param Service result type + * @return ServicePipeline The service pipeline instance + */ + public ServicePipeline registerServiceImplementation( + @Nonnull final Class> type, + @Nonnull final Service implementation, + @Nonnull final Collection> filters) { + return registerServiceImplementation(TypeToken.of(type), implementation, filters); + } + + /** + * Start traversing the pipeline by providing the context that will be used to generate the + * results + * + * @param context Context + * @param Context type + * @return Service pumper instance + */ + @Nonnull + public ServicePump pump(@Nonnull final Context context) { + return new ServicePump<>(this, context); + } + + @SuppressWarnings("unchecked") + @Nonnull + ServiceRepository getRepository( + @Nonnull final TypeToken> type) { + final ServiceRepository repository = + (ServiceRepository) this.repositories.get(type); + if (repository == null) { + throw new IllegalArgumentException( + String.format("No service registered for type '%s'", type.toString())); + } + return repository; + } + + /** + * Get a collection of all the recognised service types. + * + * @return Returns an Immutable collection of the service types registered. + */ + @Nonnull + public Collection>> getRecognizedTypes() { + return Collections.unmodifiableCollection(this.repositories.keySet()); + } + + /** + * Get a collection of all the {@link TypeToken} of all implementations for a given type. + * + * @param type The {@link TypeToken} of the service to get implementations for. + * @param The context type. + * @param The result type. + * @param The service type. + * @return Returns an collection of the {@link TypeToken}s of the implementations for a given + * service. Iterator order matches the priority when pumping contexts through the pipeline + */ + @Nonnull + @SuppressWarnings("unchecked") + public > Collection> getImplementations( + @Nonnull final TypeToken type) { + ServiceRepository repository = getRepository(type); + List> collection = new LinkedList<>(); + final LinkedList.ServiceWrapper>> + queue = repository.getQueue(); + queue.sort(null); + Collections.reverse(queue); + for (ServiceRepository.ServiceWrapper> wrapper : queue) { + collection + .add((TypeToken) TypeToken.of(wrapper.getImplementation().getClass())); + } + return Collections.unmodifiableList(collection); + } + + @Nonnull + Executor getExecutor() { + return this.executor; + } + +} diff --git a/cloud-services/src/main/java/cloud/commandframework/services/ServicePipelineBuilder.java b/cloud-services/src/main/java/cloud/commandframework/services/ServicePipelineBuilder.java new file mode 100644 index 00000000..866d5975 --- /dev/null +++ b/cloud-services/src/main/java/cloud/commandframework/services/ServicePipelineBuilder.java @@ -0,0 +1,66 @@ +// +// MIT License +// +// Copyright (c) 2020 IntellectualSites +// +// 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 cloud.commandframework.services; + +import com.google.common.base.Preconditions; + +import javax.annotation.Nonnull; +import java.util.concurrent.Executor; +import java.util.concurrent.Executors; + +/** + * Builder for {@link ServicePipeline} + */ +@SuppressWarnings("unused") +public final class ServicePipelineBuilder { + + private Executor executor = Executors.newSingleThreadExecutor(); + + ServicePipelineBuilder() { + } + + /** + * Construct a new {@link ServicePipeline} using the options specified in the builder + * + * @return New service pipeline + */ + @Nonnull + public ServicePipeline build() { + return new ServicePipeline(this.executor); + } + + /** + * Set the executor that will be used by the pipeline when evaluating results asynchronously. + * Unless specified, the pipeline will use a single threaded executor. + * + * @param executor New executor + * @return Builder instance + */ + @Nonnull + public ServicePipelineBuilder withExecutor(@Nonnull final Executor executor) { + this.executor = Preconditions.checkNotNull(executor, "Executor may not be null"); + return this; + } + +} diff --git a/cloud-services/src/main/java/cloud/commandframework/services/ServicePump.java b/cloud-services/src/main/java/cloud/commandframework/services/ServicePump.java new file mode 100644 index 00000000..5d7f91f9 --- /dev/null +++ b/cloud-services/src/main/java/cloud/commandframework/services/ServicePump.java @@ -0,0 +1,72 @@ +// +// MIT License +// +// Copyright (c) 2020 IntellectualSites +// +// 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 cloud.commandframework.services; + +import com.google.common.reflect.TypeToken; +import cloud.commandframework.services.types.Service; + +import javax.annotation.Nonnull; + +/** + * Class that forwards a context to a service type that consumes said context + * + * @param Context to pump + */ +public final class ServicePump { + + private final ServicePipeline servicePipeline; + private final Context context; + + ServicePump(final ServicePipeline servicePipeline, final Context context) { + this.servicePipeline = servicePipeline; + this.context = context; + } + + /** + * Specify the service type that the context will be pumped through + * + * @param type Service type + * @param Result type + * @return Service spigot instance + */ + @Nonnull + public ServiceSpigot through( + @Nonnull final TypeToken> type) { + return new ServiceSpigot<>(this.servicePipeline, this.context, type); + } + + /** + * Specify the service type that the context will be pumped through + * + * @param clazz Service type + * @param Result type + * @return Service spigot instance + */ + @Nonnull + public ServiceSpigot through( + @Nonnull final Class> clazz) { + return this.through(TypeToken.of(clazz)); + } + +} diff --git a/cloud-services/src/main/java/cloud/commandframework/services/annotations/Order.java b/cloud-services/src/main/java/cloud/commandframework/services/annotations/Order.java new file mode 100644 index 00000000..0d6792bc --- /dev/null +++ b/cloud-services/src/main/java/cloud/commandframework/services/annotations/Order.java @@ -0,0 +1,46 @@ +// +// MIT License +// +// Copyright (c) 2020 IntellectualSites +// +// 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 cloud.commandframework.services.annotations; + +import cloud.commandframework.services.ExecutionOrder; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * Used to specify the relative priority of a service implementation + */ +@Target({ElementType.TYPE, ElementType.METHOD}) +@Retention(RetentionPolicy.RUNTIME) +public @interface Order { + + /** + * The priority of the implementation + * + * @return Priority + */ + ExecutionOrder value() default ExecutionOrder.SOON; +} diff --git a/cloud-services/src/main/java/cloud/commandframework/services/annotations/package-info.java b/cloud-services/src/main/java/cloud/commandframework/services/annotations/package-info.java new file mode 100644 index 00000000..feb91864 --- /dev/null +++ b/cloud-services/src/main/java/cloud/commandframework/services/annotations/package-info.java @@ -0,0 +1,28 @@ +// +// MIT License +// +// Copyright (c) 2020 IntellectualSites +// +// 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. +// + +/** + * Service related annotations + */ +package cloud.commandframework.services.annotations;