diff --git a/src/main/java/com/helion3/prism/Prism.java b/src/main/java/com/helion3/prism/Prism.java index 384ffa1..5e3fedd 100644 --- a/src/main/java/com/helion3/prism/Prism.java +++ b/src/main/java/com/helion3/prism/Prism.java @@ -45,6 +45,8 @@ import com.helion3.prism.api.parameters.ParameterRadius; import com.helion3.prism.api.parameters.ParameterTime; import com.helion3.prism.api.records.ActionableResult; +import com.helion3.prism.api.services.PrismService; +import com.helion3.prism.api.services.PrismServiceImpl; import com.helion3.prism.api.storage.StorageAdapter; import com.helion3.prism.commands.PrismCommands; import com.helion3.prism.configuration.Config; @@ -179,6 +181,7 @@ public void onInitialization(GameInitializationEvent event) { @Listener public void onPostInitialization(GamePostInitializationEvent event) { getConfiguration().saveConfiguration(); + Sponge.getServiceManager().setProvider(this, PrismService.class, new PrismServiceImpl()); } @Listener diff --git a/src/main/java/com/helion3/prism/api/services/PrismService.java b/src/main/java/com/helion3/prism/api/services/PrismService.java new file mode 100644 index 0000000..4b71f53 --- /dev/null +++ b/src/main/java/com/helion3/prism/api/services/PrismService.java @@ -0,0 +1,39 @@ +package com.helion3.prism.api.services; + +import org.spongepowered.api.command.CommandSource; + +import javax.annotation.Nonnull; + +public interface PrismService { + + /** + * Queries all events matching the conditions in the {@link Request} and restores the + * original states within every event. + * + * @param source The source requesting the rollback maneuver + * @param conditions The collection of all parameters with which to filter out logged events + * @throws Exception if something unexpected happens + */ + void rollback(@Nonnull CommandSource source, @Nonnull Request conditions) throws Exception; + + /** + * Queries all events matching the conditions in the {@link Request} and restores the + * final states within every event. + * + * @param source The source requesting the restoration maneuver + * @param conditions The collection of all parameters with which to filter out logged events + * @throws Exception if something unexpected happens + */ + void restore(@Nonnull CommandSource source, @Nonnull Request conditions) throws Exception; + + /** + * Queries all events matching the conditions in the {@link Request} and sends + * the command source the matching information. + * + * @param source The source requesting the information + * @param conditions The collection of all parameters with which to filter out logged events + * @throws Exception if something unexpected happens + */ + void lookup(@Nonnull CommandSource source, @Nonnull Request conditions) throws Exception; + +} diff --git a/src/main/java/com/helion3/prism/api/services/PrismServiceImpl.java b/src/main/java/com/helion3/prism/api/services/PrismServiceImpl.java new file mode 100644 index 0000000..b259639 --- /dev/null +++ b/src/main/java/com/helion3/prism/api/services/PrismServiceImpl.java @@ -0,0 +1,129 @@ +package com.helion3.prism.api.services; + +import com.helion3.prism.api.flags.Flag; +import com.helion3.prism.api.query.ConditionGroup; +import com.helion3.prism.api.query.FieldCondition; +import com.helion3.prism.api.query.MatchRule; +import com.helion3.prism.api.query.QuerySession; +import com.helion3.prism.api.query.Sort; +import com.helion3.prism.commands.ApplierCommand; +import com.helion3.prism.util.AsyncUtil; +import com.helion3.prism.util.DataQueries; +import org.spongepowered.api.command.CommandSource; + +import javax.annotation.Nonnull; +import java.util.regex.Pattern; + +public class PrismServiceImpl implements PrismService { + + @Override + public void rollback(@Nonnull CommandSource source, @Nonnull Request conditions) { + QuerySession session = buildSession(source, conditions); + session.addFlag(Flag.NO_GROUP); + ApplierCommand.runApplier(session, Sort.NEWEST_FIRST); + } + + @Override + public void restore(@Nonnull CommandSource source, @Nonnull Request conditions) { + QuerySession session = buildSession(source, conditions); + session.addFlag(Flag.NO_GROUP); + ApplierCommand.runApplier(session, Sort.OLDEST_FIRST); + } + + @Override + public void lookup(@Nonnull CommandSource source, @Nonnull Request conditions) { + AsyncUtil.lookup(buildSession(source, conditions)); + } + + private QuerySession buildSession(CommandSource source, Request conditions) { + final QuerySession session = new QuerySession(source); + com.helion3.prism.api.query.Query query = session.newQuery(); + + ConditionGroup eventConditionGroup = new ConditionGroup(ConditionGroup.Operator.OR); + conditions.getEvents().forEach(event -> + eventConditionGroup.add(FieldCondition.of( + DataQueries.EventName, + MatchRule.EQUALS, + event.getId()))); + if (!eventConditionGroup.getConditions().isEmpty()) { + query.addCondition(eventConditionGroup); + } + + ConditionGroup targetConditionGroup = new ConditionGroup(ConditionGroup.Operator.OR); + conditions.getTargets().forEach(target -> + targetConditionGroup.add(FieldCondition.of( + DataQueries.Target, + MatchRule.EQUALS, + Pattern.compile(target.replace("_", " "))))); + if (!targetConditionGroup.getConditions().isEmpty()) { + query.addCondition(targetConditionGroup); + } + + ConditionGroup playerConditionGroup = new ConditionGroup(ConditionGroup.Operator.OR); + conditions.getPlayerUuids().forEach(uuid -> + playerConditionGroup.add(FieldCondition.of( + DataQueries.Player, + MatchRule.EQUALS, + uuid.toString()))); + if (!playerConditionGroup.getConditions().isEmpty()) { + query.addCondition(playerConditionGroup); + } + + ConditionGroup worldConditionGroup = new ConditionGroup(ConditionGroup.Operator.OR); + conditions.getWorldUuids().forEach(uuid -> + worldConditionGroup.add(FieldCondition.of( + DataQueries.Location.then(DataQueries.WorldUuid), + MatchRule.EQUALS, + uuid.toString()))); + if (!worldConditionGroup.getConditions().isEmpty()) { + query.addCondition(worldConditionGroup); + } + + conditions.getxRange().ifPresent(range -> { + query.addCondition(FieldCondition.of( + DataQueries.X, + MatchRule.GREATER_THAN_EQUAL, + range.lowerEndpoint())); + query.addCondition(FieldCondition.of( + DataQueries.X, + MatchRule.LESS_THAN_EQUAL, + range.upperEndpoint())); + }); + conditions.getyRange().ifPresent(range -> { + query.addCondition(FieldCondition.of( + DataQueries.Y, + MatchRule.GREATER_THAN_EQUAL, + range.lowerEndpoint())); + query.addCondition(FieldCondition.of( + DataQueries.Y, + MatchRule.LESS_THAN_EQUAL, + range.upperEndpoint())); + }); + conditions.getzRange().ifPresent(range -> { + query.addCondition(FieldCondition.of( + DataQueries.Z, + MatchRule.GREATER_THAN_EQUAL, + range.lowerEndpoint())); + query.addCondition(FieldCondition.of( + DataQueries.Z, + MatchRule.LESS_THAN_EQUAL, + range.upperEndpoint())); + }); + conditions.getEarliest().ifPresent(earliest -> + query.addCondition(FieldCondition.of( + DataQueries.Created, + MatchRule.GREATER_THAN_EQUAL, + earliest))); + conditions.getLatest().ifPresent(latest -> + query.addCondition(FieldCondition.of( + DataQueries.Created, + MatchRule.LESS_THAN_EQUAL, + latest))); + conditions.getFlags() + .stream() + .filter(o -> o instanceof Flag) + .map(o -> (Flag) o) + .forEach(session::addFlag); + return session; + } +} diff --git a/src/main/java/com/helion3/prism/api/services/Request.java b/src/main/java/com/helion3/prism/api/services/Request.java new file mode 100644 index 0000000..356036b --- /dev/null +++ b/src/main/java/com/helion3/prism/api/services/Request.java @@ -0,0 +1,203 @@ +package com.helion3.prism.api.services; + +import com.google.common.base.Preconditions; +import com.google.common.collect.Range; +import com.google.common.collect.Sets; +import com.helion3.prism.api.data.PrismEvent; + +import javax.annotation.Nonnull; +import java.io.Serializable; +import java.util.Date; +import java.util.Optional; +import java.util.Set; +import java.util.UUID; + +/** + * A store for all filtering information necessary to locate logged events. + */ +public final class Request implements Serializable { + + private static final long serialVersionUID = 4369983541428028962L; + + /** + * Create a {@link Request.Builder} to construct a {@link Request} to use in a {@link PrismService}. + * + * @return a builder + */ + @Nonnull + public static Builder builder() { + return new Builder(); + } + + private final Set events; + private final Set targets; + private final Set playerUuids; + private final Set worldUuids; + private final Range xRange; + private final Range yRange; + private final Range zRange; + private final Date earliest; + private final Date latest; + + private final Set flags; + + private Request(@Nonnull Set events, + @Nonnull Set targets, + @Nonnull Set playerUuids, + @Nonnull Set worldUuids, + Range xRange, + Range yRange, + Range zRange, + Date earliest, + Date latest, + @Nonnull Set flags) { + this.events = events; + this.targets = targets; + this.playerUuids = playerUuids; + this.worldUuids = worldUuids; + this.xRange = xRange; + this.yRange = yRange; + this.zRange = zRange; + this.earliest = earliest; + this.latest = latest; + this.flags = flags; + } + + @Nonnull + public Set getEvents() { + return events; + } + + @Nonnull + public Set getTargets() { + return targets; + } + + @Nonnull + public Set getPlayerUuids() { + return playerUuids; + } + + @Nonnull + public Set getWorldUuids() { + return worldUuids; + } + + @Nonnull + public Optional> getxRange() { + return Optional.ofNullable(xRange); + } + + @Nonnull + public Optional> getyRange() { + return Optional.ofNullable(yRange); + } + + @Nonnull + public Optional> getzRange() { + return Optional.ofNullable(zRange); + } + + @Nonnull + public Optional getEarliest() { + return Optional.ofNullable(earliest); + } + + @Nonnull + public Optional getLatest() { + return Optional.ofNullable(latest); + } + + @Nonnull + public Set getFlags() { + return flags; + } + + public static class Builder { + + private Set events = Sets.newHashSet(); + private Set targets = Sets.newHashSet(); + private Set playerUuids = Sets.newHashSet(); + private Set worldUuids = Sets.newHashSet(); + private Range xRange = null; + private Range yRange = null; + private Range zRange = null; + private Date earliest = null; + private Date latest = null; + private Set flags = Sets.newHashSet(); + + private Builder() { + } + + public Request build() { + return new Request(events, targets, playerUuids, worldUuids, xRange, yRange, zRange, earliest, latest, flags); + } + + @SuppressWarnings("unused") + public Builder addEvent(@Nonnull PrismEvent event) { + Preconditions.checkNotNull(event); + this.events.add(event); + return this; + } + + @SuppressWarnings("unused") + public Builder addTarget(@Nonnull String target) { + Preconditions.checkNotNull(target); + this.targets.add(target); + return this; + } + + @SuppressWarnings("unused") + public Builder addPlayerUuid(@Nonnull UUID playerUuid) { + Preconditions.checkNotNull(playerUuid); + this.playerUuids.add(playerUuid); + return this; + } + + @SuppressWarnings("unused") + public Builder addWorldUuid(@Nonnull UUID worldUuid) { + Preconditions.checkNotNull(worldUuid); + this.worldUuids.add(worldUuid); + return this; + } + + @SuppressWarnings("unused") + public Builder setxRange(int lower, int upper) { + this.xRange = Range.closed(lower, upper); + return this; + } + + @SuppressWarnings("unused") + public Builder setyRange(int lower, int upper) { + this.yRange = Range.closed(lower, upper); + return this; + } + + @SuppressWarnings("unused") + public Builder setzRange(int lower, int upper) { + this.zRange = Range.closed(lower, upper); + return this; + } + + @SuppressWarnings("unused") + public Builder setEarliest(Date earliest) { + this.earliest = earliest; + return this; + } + + @SuppressWarnings("unused") + public Builder setLatest(Date latest) { + this.latest = latest; + return this; + } + + @SuppressWarnings("unused") + public Builder addFlag(@Nonnull Object flag) { + Preconditions.checkNotNull(flag); + this.flags.add(flag); + return this; + } + + } + +}