package io.airlift.airship.coordinator;
import com.google.common.base.Function;
import com.google.common.base.Optional;
import com.google.common.base.Preconditions;
import com.google.common.base.Predicate;
import com.google.common.base.Predicates;
import com.google.common.collect.Collections2;
import com.google.common.collect.Lists;
import io.airlift.airship.shared.HttpUriBuilder;
import io.airlift.airship.shared.SlotLifecycleState;
import io.airlift.airship.shared.SlotStatus;
import javax.annotation.Nullable;
import javax.ws.rs.core.UriInfo;
import java.net.URI;
import java.util.Collection;
import java.util.List;
import java.util.Map.Entry;
import java.util.UUID;
import static com.google.common.base.Functions.compose;
import static io.airlift.airship.coordinator.StringFunctions.startsWith;
import static io.airlift.airship.coordinator.StringFunctions.toLowerCase;
import static java.lang.String.format;
public class SlotFilterBuilder
{
public static SlotFilterBuilder builder()
{
return new SlotFilterBuilder();
}
public static Predicate<SlotStatus> build(UriInfo uriInfo, boolean filterRequired, List<UUID> allUuids)
{
SlotFilterBuilder builder = new SlotFilterBuilder();
for (Entry<String, List<String>> entry : uriInfo.getQueryParameters().entrySet()) {
if ("state".equals(entry.getKey())) {
for (String stateFilter : entry.getValue()) {
builder.addStateFilter(stateFilter);
}
}
else if ("!state".equals(entry.getKey())) {
for (String stateFilter : entry.getValue()) {
builder.addNotStateFilter(stateFilter);
}
}
else if ("host".equals(entry.getKey())) {
for (String hostGlob : entry.getValue()) {
builder.addHostGlobFilter(hostGlob);
}
}
else if ("!host".equals(entry.getKey())) {
for (String hostGlob : entry.getValue()) {
builder.addNotHostGlobFilter(hostGlob);
}
}
else if ("machine".equals(entry.getKey())) {
for (String machineGlob : entry.getValue()) {
builder.addMachineGlobFilter(machineGlob);
}
}
else if ("!machine".equals(entry.getKey())) {
for (String machineGlob : entry.getValue()) {
builder.addNotMachineGlobFilter(machineGlob);
}
}
else if ("uuid".equals(entry.getKey())) {
for (String shortId : entry.getValue()) {
builder.addSlotUuidFilter(shortId);
}
}
else if ("!uuid".equals(entry.getKey())) {
for (String shortId : entry.getValue()) {
builder.addNotSlotUuidFilter(shortId);
}
}
else if ("binary".equals(entry.getKey())) {
for (String binaryGlob : entry.getValue()) {
builder.addBinaryGlobFilter(binaryGlob);
}
}
else if ("!binary".equals(entry.getKey())) {
for (String binaryGlob : entry.getValue()) {
builder.addNotBinaryGlobFilter(binaryGlob);
}
}
else if ("config".equals(entry.getKey())) {
for (String configGlob : entry.getValue()) {
builder.addConfigGlobFilter(configGlob);
}
}
else if ("!config".equals(entry.getKey())) {
for (String configGlob : entry.getValue()) {
builder.addNotConfigGlobFilter(configGlob);
}
}
else if ("all".equals(entry.getKey())) {
builder.selectAll();
}
}
return builder.buildPredicate(filterRequired, allUuids);
}
private final List<SlotLifecycleState> stateFilters = Lists.newArrayListWithCapacity(6);
private final List<SlotLifecycleState> notStateFilters = Lists.newArrayListWithCapacity(6);
private final List<String> slotUuidFilters = Lists.newArrayListWithCapacity(6);
private final List<String> notSlotUuidFilters = Lists.newArrayListWithCapacity(6);
private final List<String> hostGlobs = Lists.newArrayListWithCapacity(6);
private final List<String> notHostGlobs = Lists.newArrayListWithCapacity(6);
private final List<String> machineGlobs = Lists.newArrayListWithCapacity(6);
private final List<String> notMachineGlobs = Lists.newArrayListWithCapacity(6);
private final List<String> binaryGlobs = Lists.newArrayListWithCapacity(6);
private final List<String> notBinaryGlobs = Lists.newArrayListWithCapacity(6);
private final List<String> configGlobs = Lists.newArrayListWithCapacity(6);
private final List<String> notConfigGlobs = Lists.newArrayListWithCapacity(6);
private boolean selectAll;
private SlotFilterBuilder()
{
}
public SlotFilterBuilder addStateFilter(String stateFilter)
{
Preconditions.checkNotNull(stateFilter, "stateFilter is null");
SlotLifecycleState state = SlotLifecycleState.lookup(stateFilter);
Preconditions.checkArgument(state != null, "unknown state %s", stateFilter);
stateFilters.add(state);
return this;
}
public SlotFilterBuilder addNotStateFilter(String notStateFilter)
{
Preconditions.checkNotNull(notStateFilter, "notStateFilter is null");
SlotLifecycleState state = SlotLifecycleState.lookup(notStateFilter);
Preconditions.checkArgument(state != null, "unknown state %s", notStateFilter);
notStateFilters.add(state);
return this;
}
public SlotFilterBuilder addSlotUuidFilter(String shortId)
{
Preconditions.checkNotNull(shortId, "shortId is null");
slotUuidFilters.add(shortId);
return this;
}
public SlotFilterBuilder addNotSlotUuidFilter(String notShortId)
{
Preconditions.checkNotNull(notShortId, "notShortId is null");
notSlotUuidFilters.add(notShortId);
return this;
}
public SlotFilterBuilder addHostGlobFilter(String hostGlob)
{
Preconditions.checkNotNull(hostGlob, "hostGlob is null");
hostGlobs.add(hostGlob);
return this;
}
public SlotFilterBuilder addNotHostGlobFilter(String notHostGlob)
{
Preconditions.checkNotNull(notHostGlob, "notHostGlob is null");
notHostGlobs.add(notHostGlob);
return this;
}
public void addMachineGlobFilter(String machineGlob)
{
Preconditions.checkNotNull(machineGlob, "machineGlob is null");
machineGlobs.add(machineGlob);
}
public void addNotMachineGlobFilter(String notMachineGlob)
{
Preconditions.checkNotNull(notMachineGlob, "notMachineGlob is null");
notMachineGlobs.add(notMachineGlob);
}
public SlotFilterBuilder addBinaryGlobFilter(String binaryGlob)
{
Preconditions.checkNotNull(binaryGlob, "binaryGlob is null");
binaryGlobs.add(binaryGlob);
return this;
}
public SlotFilterBuilder addNotBinaryGlobFilter(String notBinaryGlob)
{
Preconditions.checkNotNull(notBinaryGlob, "notBinaryGlob is null");
notBinaryGlobs.add(notBinaryGlob);
return this;
}
public SlotFilterBuilder addConfigGlobFilter(String configGlob)
{
Preconditions.checkNotNull(configGlob, "configGlob is null");
configGlobs.add(configGlob);
return this;
}
public SlotFilterBuilder addNotConfigGlobFilter(String notConfigGlob)
{
Preconditions.checkNotNull(notConfigGlob, "notConfigGlob is null");
notConfigGlobs.add(notConfigGlob);
return this;
}
public SlotFilterBuilder selectAll()
{
this.selectAll = true;
return this;
}
public Predicate<SlotStatus> buildPredicate(boolean filterRequired, final List<UUID> allUuids)
{
Optional<Predicate<SlotStatus>> includesPredicate = buildIncludesPredicate(allUuids);
Optional<Predicate<SlotStatus>> excludesPredicate = buildExcludesPredicate(allUuids);
// if filter is required, make sure we got an include or exclude
if (filterRequired && !includesPredicate.isPresent() && !excludesPredicate.isPresent()) {
throw new InvalidSlotFilterException();
}
Predicate<SlotStatus> include = includesPredicate.or(Predicates.<SlotStatus>alwaysTrue());
if (excludesPredicate.isPresent()) {
// includes and not excluded
return Predicates.and(include, Predicates.not(excludesPredicate.get()));
}
return include;
}
private Optional<Predicate<SlotStatus>> buildIncludesPredicate(final List<UUID> allUuids)
{
// Filters are evaluated as: (uuid || uuid || uuid) && (state || state || state) && etc.
List<Predicate<SlotStatus>> andPredicates = Lists.newArrayListWithCapacity(6);
if (!slotUuidFilters.isEmpty()) {
Predicate<SlotStatus> predicate = Predicates.or(Lists.transform(slotUuidFilters, new Function<String, Predicate<SlotStatus>>()
{
@Override
public Predicate<SlotStatus> apply(String shortId)
{
return new SlotUuidPredicate(shortId, allUuids);
}
}));
andPredicates.add(predicate);
}
if (!stateFilters.isEmpty()) {
Predicate<SlotStatus> predicate = Predicates.or(Lists.transform(stateFilters, new Function<SlotLifecycleState, StatePredicate>()
{
@Override
public StatePredicate apply(SlotLifecycleState state)
{
return new StatePredicate(state);
}
}));
andPredicates.add(predicate);
}
if (!hostGlobs.isEmpty()) {
Predicate<SlotStatus> predicate = Predicates.or(Lists.transform(hostGlobs, new Function<String, HostPredicate>()
{
@Override
public HostPredicate apply(String hostGlob)
{
return new HostPredicate(hostGlob);
}
}));
andPredicates.add(predicate);
}
if (!machineGlobs.isEmpty()) {
Predicate<SlotStatus> predicate = Predicates.or(Lists.transform(machineGlobs, new Function<String, MachinePredicate>()
{
@Override
public MachinePredicate apply(String machineGlob)
{
return new MachinePredicate(machineGlob);
}
}));
andPredicates.add(predicate);
}
if (!binaryGlobs.isEmpty()) {
Predicate<SlotStatus> predicate = Predicates.or(Lists.transform(binaryGlobs, new Function<String, BinarySpecPredicate>()
{
@Override
public BinarySpecPredicate apply(String binarySpecPredicate)
{
return new BinarySpecPredicate(binarySpecPredicate);
}
}));
andPredicates.add(predicate);
}
if (!configGlobs.isEmpty()) {
Predicate<SlotStatus> predicate = Predicates.or(Lists.transform(configGlobs, new Function<String, ConfigSpecPredicate>()
{
@Override
public ConfigSpecPredicate apply(String configSpecPredicate)
{
return new ConfigSpecPredicate(configSpecPredicate);
}
}));
andPredicates.add(predicate);
}
// we build all the explicit predicates even if "all" was specified to catch errors
if (selectAll) {
return Optional.of(Predicates.<SlotStatus>alwaysTrue());
}
else if (!andPredicates.isEmpty()) {
return Optional.of(Predicates.and(andPredicates));
}
return Optional.absent();
}
private Optional<Predicate<SlotStatus>> buildExcludesPredicate(final List<UUID> allUuids)
{
// If the slot matches any of the excludes it will not be considered
// Filters are evaluated as: !uuid || !uuid || !uuid || !state || !state || !state
List<Predicate<SlotStatus>> excludes = Lists.newArrayListWithCapacity(6);
excludes.addAll(Lists.transform(notSlotUuidFilters, new Function<String, Predicate<SlotStatus>>()
{
@Override
public Predicate<SlotStatus> apply(String shortId)
{
return new SlotUuidPredicate(shortId, allUuids);
}
}));
excludes.addAll(Lists.transform(notStateFilters, new Function<SlotLifecycleState, StatePredicate>()
{
@Override
public StatePredicate apply(SlotLifecycleState state)
{
return new StatePredicate(state);
}
}));
excludes.addAll(Lists.transform(notHostGlobs, new Function<String, HostPredicate>()
{
@Override
public HostPredicate apply(String hostGlob)
{
return new HostPredicate(hostGlob);
}
}));
excludes.addAll(Lists.transform(notMachineGlobs, new Function<String, MachinePredicate>()
{
@Override
public MachinePredicate apply(String machineGlob)
{
return new MachinePredicate(machineGlob);
}
}));
excludes.addAll(Lists.transform(notBinaryGlobs, new Function<String, BinarySpecPredicate>()
{
@Override
public BinarySpecPredicate apply(String binarySpecPredicate)
{
return new BinarySpecPredicate(binarySpecPredicate);
}
}));
excludes.addAll(Lists.transform(notConfigGlobs, new Function<String, ConfigSpecPredicate>()
{
@Override
public ConfigSpecPredicate apply(String configSpecPredicate)
{
return new ConfigSpecPredicate(configSpecPredicate);
}
}));
if (excludes.isEmpty()) {
return Optional.absent();
}
return Optional.of(Predicates.or(excludes));
}
public URI buildUri(URI baseUri)
{
HttpUriBuilder uriBuilder = HttpUriBuilder.uriBuilderFrom(baseUri);
return buildUri(uriBuilder);
}
public URI buildUri(HttpUriBuilder uriBuilder)
{
for (String binaryGlob : binaryGlobs) {
uriBuilder.addParameter("binary", binaryGlob);
}
for (String notBinaryGlob : notBinaryGlobs) {
uriBuilder.addParameter("!binary", notBinaryGlob);
}
for (String configGlob : configGlobs) {
uriBuilder.addParameter("config", configGlob);
}
for (String notConfigGlob : notConfigGlobs) {
uriBuilder.addParameter("!config", notConfigGlob);
}
for (String hostGlob : hostGlobs) {
uriBuilder.addParameter("host", hostGlob);
}
for (String notHostGlob : notHostGlobs) {
uriBuilder.addParameter("!host", notHostGlob);
}
for (String machineGlob : machineGlobs) {
uriBuilder.addParameter("machine", machineGlob);
}
for (String notMachineGlob : notMachineGlobs) {
uriBuilder.addParameter("!machine", notMachineGlob);
}
for (SlotLifecycleState stateFilter : stateFilters) {
uriBuilder.addParameter("state", stateFilter.name());
}
for (SlotLifecycleState notStateFilter : notStateFilters) {
uriBuilder.addParameter("!state", notStateFilter.name());
}
for (String shortId : slotUuidFilters) {
uriBuilder.addParameter("uuid", shortId);
}
for (String notShortId : notSlotUuidFilters) {
uriBuilder.addParameter("!uuid", notShortId);
}
if (selectAll) {
uriBuilder.addParameter("all");
}
return uriBuilder.build();
}
public static class SlotUuidPredicate
implements Predicate<SlotStatus>
{
private final UUID uuid;
public SlotUuidPredicate(String shortId, List<UUID> allUuids)
{
Predicate<UUID> startsWithPrefix = Predicates.compose(startsWith(shortId.toLowerCase()), compose(toLowerCase(), StringFunctions.<UUID>toStringFunction()));
Collection<UUID> matches = Collections2.filter(allUuids, startsWithPrefix);
if (matches.size() > 1) {
throw new IllegalArgumentException(format("Ambiguous expansion for id '%s': %s", shortId, matches));
}
if (matches.isEmpty()) {
uuid = null;
}
else {
uuid = matches.iterator().next();
}
}
public SlotUuidPredicate(UUID uuid)
{
this.uuid = uuid;
}
@Override
public boolean apply(@Nullable SlotStatus slotStatus)
{
return slotStatus != null &&
slotStatus.getId() != null &&
uuid != null &&
uuid.equals(slotStatus.getId());
}
}
public static class HostPredicate
implements Predicate<SlotStatus>
{
private final UriHostPredicate predicate;
public HostPredicate(String hostGlob)
{
predicate = new UriHostPredicate(hostGlob.toLowerCase());
}
@Override
public boolean apply(@Nullable SlotStatus slotStatus)
{
return slotStatus != null &&
(predicate.apply(slotStatus.getExternalUri()) || predicate.apply(slotStatus.getSelf()));
}
}
public static class MachinePredicate
implements Predicate<SlotStatus>
{
private final GlobPredicate predicate;
public MachinePredicate(String machineGlob)
{
predicate = new GlobPredicate(machineGlob);
}
@Override
public boolean apply(@Nullable SlotStatus slotStatus)
{
return slotStatus != null && predicate.apply(slotStatus.getInstanceId());
}
}
public static class StatePredicate
implements Predicate<SlotStatus>
{
private final SlotLifecycleState state;
public StatePredicate(SlotLifecycleState state)
{
this.state = state;
}
@Override
public boolean apply(SlotStatus slotStatus)
{
return slotStatus.getState() == state;
}
}
public static class BinarySpecPredicate
implements Predicate<SlotStatus>
{
private final Predicate<CharSequence> glob;
public BinarySpecPredicate(String binaryFilter)
{
glob = new GlobPredicate("*" + binaryFilter + "*");
}
@Override
public boolean apply(@Nullable SlotStatus slotStatus)
{
return (slotStatus != null) &&
(slotStatus.getAssignment() != null) &&
glob.apply(slotStatus.getAssignment().getBinary());
}
}
public static class ConfigSpecPredicate
implements Predicate<SlotStatus>
{
private final Predicate<CharSequence> glob;
public ConfigSpecPredicate(String configFilter)
{
glob = new GlobPredicate("*" + configFilter + "*");
}
@Override
public boolean apply(@Nullable SlotStatus slotStatus)
{
return (slotStatus != null) &&
(slotStatus.getAssignment() != null) &&
glob.apply(slotStatus.getAssignment().getConfig());
}
}
}