/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you 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 org.apache.brooklyn.core.config;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;
import org.apache.brooklyn.api.entity.Entity;
import org.apache.brooklyn.api.location.Location;
import org.apache.brooklyn.api.objs.BrooklynObject;
import org.apache.brooklyn.api.objs.EntityAdjunct;
import org.apache.brooklyn.config.ConfigKey;
import org.apache.brooklyn.core.objs.AbstractEntityAdjunct;
import org.apache.brooklyn.core.objs.BrooklynObjectInternal;
import org.apache.brooklyn.core.objs.BrooklynObjectPredicate;
import org.apache.brooklyn.util.guava.Maybe;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.google.common.base.Predicate;
import com.google.common.collect.Iterables;
import com.google.common.collect.Lists;
/**
* Checks configuration constraints on entities and their adjuncts.
*
* @since 0.9.0
*/
public abstract class ConfigConstraints<T extends BrooklynObject> {
public static final Logger LOG = LoggerFactory.getLogger(ConfigConstraints.class);
private final T brooklynObject;
/**
* Checks all constraints of all config keys available to an entity.
* <p>
* If a constraint is a {@link BrooklynObjectPredicate} then
* {@link BrooklynObjectPredicate#apply(Object, BrooklynObject)} will be used.
*/
public static void assertValid(Entity entity) {
Iterable<ConfigKey<?>> violations = new EntityConfigConstraints(entity).getViolations();
if (!Iterables.isEmpty(violations)) {
throw new ConstraintViolationException(errorMessage(entity, violations));
}
}
/**
* Checks all constraints of all config keys available to an entity adjunct.
* <p>
* If a constraint is a {@link BrooklynObjectPredicate} then
* {@link BrooklynObjectPredicate#apply(Object, BrooklynObject)} will be used.
*/
public static void assertValid(EntityAdjunct adjunct) {
Iterable<ConfigKey<?>> violations = new EntityAdjunctConstraints(adjunct).getViolations();
if (!Iterables.isEmpty(violations)) {
throw new ConstraintViolationException(errorMessage(adjunct, violations));
}
}
public static <T> void assertValid(Entity entity, ConfigKey<T> key, T value) {
if (!new EntityConfigConstraints(entity).isValueValid(key, value)) {
throw new ConstraintViolationException("Invalid value for " + key + " on " + entity + ": " + value);
}
}
public static <T> void assertValid(Location location, ConfigKey<T> key, T value) {
if (!new LocationConfigConstraints(location).isValueValid(key, value)) {
throw new ConstraintViolationException("Invalid value for " + key + " on " + location + ": " + value);
}
}
private static String errorMessage(BrooklynObject object, Iterable<ConfigKey<?>> violations) {
StringBuilder message = new StringBuilder("Error configuring ")
.append(object.getDisplayName())
.append(": [");
Iterator<ConfigKey<?>> it = violations.iterator();
while (it.hasNext()) {
ConfigKey<?> config = it.next();
message.append(config.getName())
.append(":")
.append(config.getConstraint());
if (it.hasNext()) {
message.append(", ");
}
}
return message.append("]").toString();
}
public ConfigConstraints(T brooklynObject) {
this.brooklynObject = brooklynObject;
}
abstract Iterable<ConfigKey<?>> getBrooklynObjectTypeConfigKeys();
public Iterable<ConfigKey<?>> getViolations() {
return validateAll();
}
@SuppressWarnings("unchecked")
private Iterable<ConfigKey<?>> validateAll() {
List<ConfigKey<?>> violating = Lists.newLinkedList();
Iterable<ConfigKey<?>> configKeys = getBrooklynObjectTypeConfigKeys();
LOG.trace("Checking config keys on {}: {}", getBrooklynObject(), configKeys);
for (ConfigKey<?> configKey : configKeys) {
BrooklynObjectInternal.ConfigurationSupportInternal configInternal = getConfigurationSupportInternal();
// getNonBlocking method coerces the value to the config key's type.
Maybe<?> maybeValue = configInternal.getNonBlocking(configKey);
if (maybeValue.isPresent()) {
// Cast is safe because the author of the constraint on the config key had to
// keep its type to Predicte<? super T>, where T is ConfigKey<T>.
ConfigKey<Object> ck = (ConfigKey<Object>) configKey;
if (!isValueValid(ck, maybeValue.get())) {
violating.add(configKey);
}
}
}
return violating;
}
@SuppressWarnings("unchecked")
<V> boolean isValueValid(ConfigKey<V> configKey, V value) {
try {
Predicate<? super V> po = configKey.getConstraint();
if (po instanceof BrooklynObjectPredicate) {
return BrooklynObjectPredicate.class.cast(po).apply(value, brooklynObject);
} else {
return po.apply(value);
}
} catch (Exception e) {
LOG.debug("Error checking constraint on " + configKey.getName(), e);
}
return true;
}
private BrooklynObjectInternal.ConfigurationSupportInternal getConfigurationSupportInternal() {
return ((BrooklynObjectInternal) brooklynObject).config();
}
protected T getBrooklynObject() {
return brooklynObject;
}
private static class EntityConfigConstraints extends ConfigConstraints<Entity> {
public EntityConfigConstraints(Entity brooklynObject) {
super(brooklynObject);
}
@Override
Iterable<ConfigKey<?>> getBrooklynObjectTypeConfigKeys() {
return getBrooklynObject().getEntityType().getConfigKeys();
}
}
private static class EntityAdjunctConstraints extends ConfigConstraints<EntityAdjunct> {
public EntityAdjunctConstraints(EntityAdjunct brooklynObject) {
super(brooklynObject);
}
@Override
Iterable<ConfigKey<?>> getBrooklynObjectTypeConfigKeys() {
return ((AbstractEntityAdjunct) getBrooklynObject()).getAdjunctType().getConfigKeys();
}
}
private static class LocationConfigConstraints extends ConfigConstraints<Location> {
public LocationConfigConstraints(Location brooklynObject) {
super(brooklynObject);
}
@Override
Iterable<ConfigKey<?>> getBrooklynObjectTypeConfigKeys() {
return Collections.emptyList();
}
}
}