/*
* 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.render;
import groovy.lang.Closure;
import java.util.Set;
import org.apache.brooklyn.api.entity.Entity;
import org.apache.brooklyn.api.sensor.AttributeSensor;
import org.apache.brooklyn.config.ConfigKey;
import org.apache.brooklyn.core.config.render.RendererHints;
import org.apache.brooklyn.util.groovy.GroovyJavaMethods;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.google.common.annotations.Beta;
import com.google.common.annotations.VisibleForTesting;
import com.google.common.base.Function;
import com.google.common.base.Functions;
import com.google.common.base.Objects;
import com.google.common.base.Optional;
import com.google.common.base.Preconditions;
import com.google.common.base.Predicates;
import com.google.common.base.Strings;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Iterables;
import com.google.common.collect.LinkedHashMultimap;
import com.google.common.collect.Multimaps;
import com.google.common.collect.SetMultimap;
import com.google.common.collect.Sets;
/**
* Registry of hints for displaying items such as sensors, e.g. in the web console.
*/
public class RendererHints {
private static final Logger log = LoggerFactory.getLogger(RendererHints.class);
private static SetMultimap<Object, Hint<?>> registry = Multimaps.synchronizedSetMultimap(LinkedHashMultimap.<Object, Hint<?>>create());
@VisibleForTesting
public static SetMultimap<Object, Hint<?>> getRegistry() { return registry; }
/**
* Registers a {@link Hint} against the given element.
* <p>
* Returns the element, for convenience when used in a with block after defining the element.
*/
public static <T> AttributeSensor<T> register(AttributeSensor<T> element, Hint<? super T> hintForThatElement) { return _register(element, hintForThatElement); }
/** as {@link #register(AttributeSensor, Hint)} */
public static <T> ConfigKey<T> register(ConfigKey<T> element, Hint<? super T> hintForThatElement) { return _register(element, hintForThatElement); }
/** as {@link #register(AttributeSensor, Hint)} */
public static <T> Class<T> register(Class<T> element, Hint<? super T> hintForThatElement) { return _register(element, hintForThatElement); }
private static <T> T _register(T element, Hint<?> hintForThatElement) {
if (element==null) {
// can happen if being done in a static initializer in an inner class
log.error("Invalid null target for renderer hint "+hintForThatElement, new Throwable("Trace for invalid null target for renderer hint"));
}
registry.put(element, hintForThatElement);
return element;
}
/** Returns all registered hints against the given element */
public static Set<Hint<?>> getHintsFor(AttributeSensor<?> element) { return _getHintsFor(element, null); }
/** as {@link #getHintsFor(AttributeSensor)} */
public static Set<Hint<?>> getHintsFor(ConfigKey<?> element) { return _getHintsFor(element, null); }
/** as {@link #getHintsFor(AttributeSensor)} */
public static Set<Hint<?>> getHintsFor(Class<?> element) { return _getHintsFor(element, null); }
@Deprecated /** @deprecated since 0.7.0 only supported for certain types */
public static Set<Hint<?>> getHintsFor(Object element) { return getHintsFor(element, null); }
@Deprecated /** @deprecated since 0.7.0 only supported for certain types */
@SuppressWarnings({ "rawtypes", "unchecked" })
public static Set<Hint<?>> getHintsFor(Object element, Class<? extends Hint> optionalHintSuperClass) { return (Set<Hint<?>>) _getHintsFor(element, optionalHintSuperClass); }
@SuppressWarnings({ "unchecked", "rawtypes" })
private static <T extends Hint> Set<T> _getHintsFor(Object element, Class<T> optionalHintSuperClass) {
Set<Hint<?>> found = ImmutableSet.copyOf(registry.get(element));
if (found.isEmpty() && element instanceof Class && !Object.class.equals(element)) {
// try superclasses of the element; this seems overkill for the main use case, Entity;
// (other classes registered are typically final)
found = (Set<Hint<?>>) _getHintsFor(((Class)element).getSuperclass(), optionalHintSuperClass);
if (found.isEmpty()) {
for (Class<?> parentInterface: ((Class)element).getInterfaces()) {
found = (Set<Hint<?>>) _getHintsFor(parentInterface, optionalHintSuperClass);
if (!found.isEmpty())
break;
}
}
}
if (optionalHintSuperClass != null) {
return (Set<T>)Sets.filter(found, Predicates.instanceOf(optionalHintSuperClass));
} else {
return (Set<T>)found;
}
}
/** Applies the (first) display value hint registered against the given target to the given initialValue */
public static Object applyDisplayValueHint(AttributeSensor<?> target, Object initialValue) { return applyDisplayValueHintUnchecked(target, initialValue); }
/** as {@link #applyDisplayValueHint(AttributeSensor, Object)} */
public static Object applyDisplayValueHint(ConfigKey<?> target, Object initialValue) { return applyDisplayValueHintUnchecked(target, initialValue); }
/** as {@link #applyDisplayValueHint(AttributeSensor, Object)} */
public static Object applyDisplayValueHint(Class<?> target, Object initialValue) { return applyDisplayValueHintUnchecked(target, initialValue); }
/** as {@link #applyDisplayValueHint(AttributeSensor, Object)}, but without type checking; public for those few cases where we may have lost the type */
@Beta
public static Object applyDisplayValueHintUnchecked(Object target, Object initialValue) { return _applyDisplayValueHint(target, initialValue, true); }
@SuppressWarnings("rawtypes")
private static Object _applyDisplayValueHint(Object target, Object initialValue, boolean includeClass) {
Iterable<RendererHints.DisplayValue> hints = RendererHints._getHintsFor(target, RendererHints.DisplayValue.class);
if (Iterables.size(hints) > 1) {
log.warn("Multiple display value hints set for {}; Only one will be applied, using first", target);
}
Optional<RendererHints.DisplayValue> hint = Optional.fromNullable(Iterables.getFirst(hints, null));
Object value = hint.isPresent() ? hint.get().getDisplayValue(initialValue) : initialValue;
if (includeClass && value!=null && !(value instanceof String) && !(value instanceof Number) && !(value.getClass().isPrimitive())) {
value = _applyDisplayValueHint(value.getClass(), value, false);
}
return value;
}
/** Parent marker class for hints. */
public static abstract class Hint<T> { }
public static interface NamedAction {
String getActionName();
}
/**
* This hint describes a named action possible on something, e.g. a sensor;
* currently used in web client to show actions on sensors
*/
public static class NamedActionWithUrl<T> extends Hint<T> implements NamedAction {
private final String actionName;
private final Function<T, String> postProcessing;
public NamedActionWithUrl(String actionName) {
this(actionName, (Function<T, String>)null);
}
@SuppressWarnings("unchecked") @Deprecated /** @deprecated since 0.7.0 use Function */
public NamedActionWithUrl(String actionName, Closure<String> postProcessing) {
this.actionName = actionName;
this.postProcessing = (Function<T, String>) ((postProcessing == null) ? null : GroovyJavaMethods.functionFromClosure(postProcessing));
}
public NamedActionWithUrl(String actionName, Function<T, String> postProcessing) {
this.actionName = actionName;
this.postProcessing = postProcessing;
}
/** @deprecated since 0.7.0 call {@link #getUrlFromValue(Object)}, parsing the sensor value yourself */ @Deprecated
public String getUrl(Entity e, AttributeSensor<T> s) {
return getUrlFromValue(e.getAttribute(s));
}
public String getActionName() {
return actionName;
}
/** this is the method invoked by web console SensorSummary, at the moment */
public String getUrlFromValue(T v) {
String v2;
if (postProcessing != null) {
v2 = postProcessing.apply(v);
} else {
v2 = (v==null ? null : v.toString());
}
if (v2 == null) return v2;
return v2.toString();
}
@Override
public int hashCode() {
return Objects.hashCode(actionName, postProcessing);
}
@Override
public boolean equals(Object obj) {
if (!(obj instanceof NamedActionWithUrl)) return false;
NamedActionWithUrl<?> o = (NamedActionWithUrl<?>) obj;
return Objects.equal(actionName, o.actionName) && Objects.equal(postProcessing, o.postProcessing);
}
}
/**
* This hint describes a transformation used to generate a display value for config keys and sensors.
* <p>
* <em><strong>Warning</strong> This is currently a {@link Beta} implementation, and
* may be changed or removed if there is a suitable alternative mechanism to achieve
* this functionality.</em>
*/
@Beta
public static class DisplayValue<T> extends Hint<T> {
private final Function<Object, String> transform;
@SuppressWarnings("unchecked")
protected DisplayValue(Function<?, String> transform) {
this.transform = (Function<Object, String>) Preconditions.checkNotNull(transform, "transform");
}
public String getDisplayValue(Object v) {
String dv = transform.apply(v);
return Strings.nullToEmpty(dv);
}
@Override
public int hashCode() {
return Objects.hashCode(transform);
}
@Override
public boolean equals(Object obj) {
if (obj == null || !(obj instanceof DisplayValue)) return false;
return Objects.equal(transform, ((DisplayValue<?>)obj).transform);
}
}
@Beta
public static <T> DisplayValue<T> displayValue(Function<T,String> transform) {
return new DisplayValue<T>(transform);
}
@Beta
public static <T> NamedActionWithUrl<T> namedActionWithUrl(String actionName, Function<T,String> transform) {
return new NamedActionWithUrl<T>(actionName, transform);
}
@Beta
public static <T> NamedActionWithUrl<T> namedActionWithUrl(String actionName) {
return new NamedActionWithUrl<T>(actionName);
}
@Beta
public static <T> NamedActionWithUrl<T> namedActionWithUrl(Function<T,String> transform) {
return openWithUrl(transform);
}
@Beta
public static <T> NamedActionWithUrl<T> namedActionWithUrl() {
return openWithUrl();
}
@Beta
public static <T> NamedActionWithUrl<T> openWithUrl() {
return openWithUrl((Function<T,String>) null);
}
@Beta
public static <T> NamedActionWithUrl<T> openWithUrl(Function<T,String> transform) {
return new NamedActionWithUrl<T>("Open", transform);
}
/**
* Forces the given sensor or config key's value to be censored. It will be
* presented as <code>********</code>.
*/
@Beta
public static <T> DisplayValue<T> censoredValue() {
return new DisplayValue<T>(Functions.constant("********"));
}
}