clocker.docker.location.strategy.affinity.AffinityRules.java Source code

Java tutorial

Introduction

Here is the source code for clocker.docker.location.strategy.affinity.AffinityRules.java

Source

/*
 * Copyright 2014-2016 by Cloudsoft Corporation Limited
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package clocker.docker.location.strategy.affinity;

import java.util.List;
import java.util.Locale;
import java.util.Queue;

import javax.annotation.Nullable;

import clocker.docker.location.DockerHostLocation;

import com.google.common.base.CharMatcher;
import com.google.common.base.Preconditions;
import com.google.common.base.Predicate;
import com.google.common.base.Predicates;
import com.google.common.base.Splitter;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.Iterables;
import com.google.common.collect.Lists;
import com.google.common.collect.Queues;
import com.google.common.reflect.TypeToken;

import org.apache.brooklyn.api.entity.Entity;
import org.apache.brooklyn.config.ConfigKey;
import org.apache.brooklyn.core.config.ConfigKeys;
import org.apache.brooklyn.core.entity.EntityPredicates;
import org.apache.brooklyn.util.javalang.Reflections;

/**
 * Affinity rules for Docker hosts.
 * <p>
 * Rules are specified as strings, formatted as follows:
 * <ul>
 * <li>(<code>NOT</code>) <code>TYPE</code> <em>entityType</em>?
 * <li>(<code>NOT</code>) <code>NAME</code> <em>entityName</em>
 * <li>(<code>NOT</code>) <code>ID</code> <em>entityId</em>?
 * <li>(<code>NOT</code>) <code>APPLICATION</code> <em>applicationId</em>?
 * <li>(<code>NOT</code>) <code>PREDICATE</code> <em>entityPredicateClass</em>
 * <li>(<code>NOT</code>) <code>EMPTY</code>
 * </ul>
 * The <code>SAME</code> token is the default behaviour, and means the entities must have the property defined in the rule, <code>NOT</code>
 * means they mustn't have the property. The parameter given specifies the type or id, and if it's missing thee rule will apply to the
 * properties of the entity being placed. Rules that take a class name will instantiate an instance of that class from the current
 * classpath, so ensure the appropriate Jar files are available. The <code>EMPTY</code> rule will treaty empty locations as allowable,
 * otherwise a new {@link DockerHostLocation} will be created for the container.
 * <p>
 * To specify a rule that there must be no entities of the same type, an entity of type SolrServer, all in the same application,
 * use these rules:
 * <pre>
 * NOT TYPE
 * TYPE brooklyn.entity.nosql.solr.SolrServer
 * SAME APPLICATION
 * </pre>
 * <p>
 * Specify the rules during configuration using the {@link #AFFINITY_RULES} key:
 * <pre>
 * - serviceType: brooklyn.entity.webapp.tomcat.TomcatServer
 *   brooklyn.config:
 *     affinity.rules:
 *     - "NOT TYPE"
 *     - "TYPE brooklyn.entity.nosql.solr.SolrServer"
 *     - "APPLICATION"
 *     - $brooklyn:formatString("NOT ID %s", $brooklyn:entity("name"))
 * </pre>
 */
public class AffinityRules implements Predicate<Entity> {

    public static final ConfigKey<List<String>> AFFINITY_RULES = ConfigKeys
            .newConfigKey(new TypeToken<List<String>>() {
            }, "affinity.rules", "Affinity rules for entity placemnent");

    public static final String NOT = "NOT";
    public static final String TYPE = "TYPE";
    public static final String NAME = "NAME";
    public static final String ID = "ID";
    public static final String APPLICATION = "APPLICATION";
    public static final String PREDICATE = "PREDICATE";
    public static final String EMPTY = "EMPTY";
    public static final Iterable<String> VERBS = ImmutableList.of(TYPE, NAME, ID, APPLICATION, PREDICATE, EMPTY);

    private Predicate<Entity> affinityRules = Predicates.alwaysTrue();
    private boolean allowEmpty = true;

    private final Entity entity;

    private AffinityRules(Entity entity) {
        this.entity = entity;
    }

    public static AffinityRules rulesFor(Entity entity) {
        return new AffinityRules(entity);
    }

    public AffinityRules parse(String... rules) {
        return parse(ImmutableList.copyOf(rules));
    }

    public AffinityRules parse(String rules) {
        return parse(Splitter.on(CharMatcher.anyOf("\n,")).omitEmptyStrings().split(rules));
    }

    public AffinityRules parse(Iterable<String> rules) {
        List<Predicate<Entity>> predicates = Lists.newArrayList();
        for (String rule : rules) {
            Predicate<Entity> predicate = predicate(rule);
            predicates.add(predicate);
        }

        affinityRules = Predicates.and(predicates);
        return this;
    }

    private Predicate<Entity> predicate(String rule) {
        Preconditions.checkNotNull(rule, "rule");
        Queue<String> tokens = Queues
                .newArrayDeque(Splitter.on(CharMatcher.WHITESPACE).omitEmptyStrings().splitToList(rule));

        boolean same = true;
        Predicate<Entity> predicate = Predicates.alwaysTrue();

        // Check first token for special values
        String first = tokens.peek();
        if (first.equalsIgnoreCase(NOT)) {
            same = false;
            tokens.remove();
        }

        // Check verb
        String verb = tokens.peek();
        if (verb == null) {
            throw new IllegalStateException("Affinity rule verb not specified: " + rule);
        } else {
            if (Iterables.contains(VERBS, verb.toUpperCase(Locale.ENGLISH))) {
                tokens.remove();
            } else {
                throw new IllegalStateException("Affinity rule parser found unexpected verb token: " + verb);
            }
        }

        // Check paramater and instantiate if required
        final String parameter = tokens.peek();
        if (parameter == null) {
            if (verb.equalsIgnoreCase(EMPTY)) {
                allowEmpty = same;
                tokens.remove();
                if (tokens.isEmpty()) {
                    return predicate;
                } else {
                    throw new IllegalStateException("Affinity rule has extra tokens: " + rule);
                }
            } else if (verb.equalsIgnoreCase(TYPE)) {
                predicate = new Predicate<Entity>() {
                    @Override
                    public boolean apply(@Nullable Entity input) {
                        return input.getEntityType().getName().equalsIgnoreCase(entity.getEntityType().getName())
                                || input.getEntityType().getSimpleName()
                                        .equalsIgnoreCase(entity.getEntityType().getSimpleName());
                    }
                };
            } else if (verb.equalsIgnoreCase(ID)) {
                predicate = EntityPredicates.idEqualTo(entity.getId());
            } else if (verb.equalsIgnoreCase(APPLICATION)) {
                predicate = EntityPredicates.applicationIdEqualTo(entity.getApplicationId());
            } else {
                throw new IllegalStateException("Affinity rule parameter not specified: " + rule);
            }
        } else {
            tokens.remove();
            if (verb.equalsIgnoreCase(TYPE)) {
                predicate = new Predicate<Entity>() {
                    @Override
                    public boolean apply(@Nullable Entity input) {
                        return input.getEntityType().getName().equalsIgnoreCase(parameter)
                                || input.getEntityType().getSimpleName().equalsIgnoreCase(parameter);
                    }
                };
            } else if (verb.equalsIgnoreCase(NAME)) {
                predicate = new Predicate<Entity>() {
                    @Override
                    public boolean apply(@Nullable Entity input) {
                        return input.getDisplayName().toLowerCase(Locale.ENGLISH)
                                .contains(parameter.toLowerCase(Locale.ENGLISH));
                    }
                };
            } else if (verb.equalsIgnoreCase(ID)) {
                predicate = EntityPredicates.idEqualTo(parameter);
            } else if (verb.equalsIgnoreCase(APPLICATION)) {
                predicate = EntityPredicates.applicationIdEqualTo(parameter);
            } else if (verb.equalsIgnoreCase(PREDICATE)) {
                try {
                    Class<?> clazz = Class.forName(parameter);
                    if (Reflections.hasNoArgConstructor(clazz)) {
                        predicate = (Predicate<Entity>) Reflections.invokeConstructorWithArgs(clazz);
                    } else {
                        throw new IllegalStateException("Could not instantiate predicate: " + parameter);
                    }
                } catch (ClassNotFoundException e) {
                    throw new IllegalStateException("Could not find predicate: " + parameter);
                }
            }
        }

        // Check for left-over tokens
        if (tokens.peek() != null) {
            throw new IllegalStateException("Affinity rule has extra tokens: " + rule);
        }

        // Create predicate and return
        if (same) {
            return predicate;
        } else {
            return Predicates.not(predicate);
        }
    }

    @Override
    public boolean apply(@Nullable Entity input) {
        return affinityRules.apply(input);
    }

    public boolean allowEmptyLocations() {
        return allowEmpty;
    }

}