/*
* Copyright (c) 2011-2017 Pivotal Software Inc, All Rights Reserved.
*
* 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 reactor.core;
import java.util.Iterator;
import java.util.Objects;
import java.util.Spliterators;
import java.util.stream.Stream;
import java.util.stream.StreamSupport;
/**
* A Scannable component exposes state in a non strictly memory consistent way and
* results should be understood as best-effort hint of the underlying state. This is
* useful to retro-engineer a component graph such as a flux operator chain via
* {@link Stream} queries from
* {@link #actuals()}, {@link #parents()} and {@link #inners()}. This allows for
* visiting patterns and possibly enable serviceability features.
* <p>
* Scannable is also a useful tool for the advanced user eager to learn which kind
* of state we usually manage in the package-scope schedulers or operators
* implementations.
*
* @author Stephane Maldini
*/
@FunctionalInterface
public interface Scannable {
/**
* A list of reserved keys for component state scanning
*/
enum Attr {
/**
* Parent key exposes the direct upstream relationship of the scanned component.
* It can be a Publisher source to an operator, a Subscription to a Subscriber
* (main flow if ambiguous with
* inner Subscriptions like flatMap), a Scheduler to a Worker.
* <p>
* {@link #parents()} can be used to navigate the parent chain.
*/
PARENT,
/**
* Delay_Error exposes a {@link Boolean} whether the scanned component
* actively supports error delaying if it manages a backlog instead of fast
* error-passing which might drop pending backlog.
* <p>
* Note: This attribute usually resolves to a constant value
*/
DELAY_ERROR,
/**
* Prefetch is an {@link Integer} attribute defining the rate of processing in a
* component
* which has capacity to request and hold a backlog of data. It
* usually maps to a component capacity when no arbitrary {@link #CAPACITY} is
* set. {@link Integer#MAX_VALUE} signal unlimited capacity and therefore
* unbounded demand.
* <p>
* Note: This attribute usually resolves to a constant value
*/
PREFETCH,
/**
* Return an an {@link Integer} capacity when no {@link #PREFETCH} is defined or
* when an arbitrary maximum limit is applied to the backlog capacity of the
* scanned component. {@link Integer#MAX_VALUE} signal unlimited capacity.
* <p>
* Note: This attribute usually resolves to a constant value
*/
CAPACITY,
/**
* The direct dependent component downstream reference if any. Operators in Flux/Mono for instance
* delegate to a target Subscriber, which is going to be the actual chain
* navigated with this reference key.
* <p>
* A reference chain downstream can be navigated via {@link #actuals()}.
*/
ACTUAL,
/**
* a {@link Throwable} attribute which indicate an error state if the scanned
* component keeps track of it.
*/
ERROR,
/**
* A {@link Integer} attribute implemented by components with a backlog
* capacity. It will expose current queue size or similar related to
* user-provided held data.
*/
BUFFERED,
/**
* A {@link Long} attribute exposing the current pending demand of a downstream
* component. Note that {@link Long#MAX_VALUE} indicates an unbounded
* (push-style) demand as specified in
* {@link org.reactivestreams.Subscription#request(long)}.
*/
REQUESTED_FROM_DOWNSTREAM,
/**
* A {@link Boolean} attribute indicating whether or not a downstream component
* has interrupted consuming this scanned component, e.g., a cancelled
* subscription. Note that it differs from {@link #TERMINATED} which is
* intended for "normal" shutdown cycles.
*/
CANCELLED,
/**
* A {@link Boolean} attribute indicating whether or not an upstream component
* terminated this scanned component. e.g. a post onComplete/onError subscriber.
* By opposition to {@link #CANCELLED} which determines if a downstream
* component interrupted this scanned component.
*/
TERMINATED;
/**
* A constant that represents {@link Scannable} returned via {@link #from(Object)}
* when the passed non-null reference is not a {@link Scannable}
*/
static final Scannable UNAVAILABLE_SCAN = new Scannable() {
@Override
public Object scan(Attr key) {
return null;
}
@Override
public boolean isScanAvailable() {
return false;
}
};
static Stream<? extends Scannable> recurse(Scannable _s, Attr key){
Scannable s = Scannable.from(_s.scan(key));
if(s == null){
return Stream.empty();
}
return StreamSupport.stream(Spliterators.spliteratorUnknownSize(new Iterator<Scannable>() {
Scannable c = s;
@Override
public boolean hasNext() {
return c != null;
}
@Override
public Scannable next() {
Scannable _c = c;
c = Scannable.from(c.scan(key));
return _c;
}
}, 0),false);
}
}
/**
* Attempt to cast the Object to a {@link Scannable}. Return null if the
* value is null, or a default object if the value is not a {@link Scannable}.
* The default object is a constant {@link Scannable} that return false on
* {@link Scannable#isScanAvailable}.
*
* @param o a reference to cast
*
* @return the casted {@link Scannable}, null or a default {@link Scannable} instance
* that isn't actually scannable.
*/
@SuppressWarnings("unchecked")
static Scannable from(Object o) {
if (o == null) {
return null;
}
if (o instanceof Scannable) {
return ((Scannable) o);
}
return Attr.UNAVAILABLE_SCAN;
}
/**
* Return a {@link Stream} navigating the {@link org.reactivestreams.Subscriber}
* chain (downward).
*
* @return a {@link Stream} navigating the {@link org.reactivestreams.Subscriber}
* chain (downward)
*/
default Stream<? extends Scannable> actuals() {
return Attr.recurse(this, Attr.ACTUAL);
}
/**
* Return a {@link Stream} of referenced inners (flatmap, multicast etc)
*
* @return a {@link Stream} of referenced inners (flatmap, multicast etc)
*/
default Stream<? extends Scannable> inners() {
return Stream.empty();
}
/**
* Return true whether the component is available for {@link #scan(Attr)} resolution.
*
* @return true whether the component is available for {@link #scan(Attr)} resolution.
*/
default boolean isScanAvailable(){
return true;
}
/**
* Return a {@link Stream} navigating the {@link org.reactivestreams.Subscription}
* chain (upward).
*
* @return a {@link Stream} navigating the {@link org.reactivestreams.Subscription}
* chain (upward)
*/
default Stream<? extends Scannable> parents() {
return Attr.recurse(this, Attr.PARENT);
}
/**
* Introspect a component's specific state {@link Attr attribute}, returning a
* best effort value or null if the attribute doesn't make sense for that particular
* component.
*
* @param key an {@link Attr} to get the operator state
*
* @return an eventual value or null
*/
Object scan(Attr key);
/**
* Introspect a component's specific state {@link Attr attribute}, returning a
* best effort value casted to the provided class, or null if the attribute doesn't
* make sense for that particular component.
*
* @param key a {@link Attr} to resolve the value within the context
* @param type the attribute type
* @return an eventual value or null if unmatched or unresolved
*
*/
default <T> T scan(Attr key, Class<T> type) {
Objects.requireNonNull(type, "type");
Object v = scan(key);
if (v == null || !type.isAssignableFrom(v.getClass())) {
return null;
}
@SuppressWarnings("unchecked")
T r = (T)v;
return r;
}
/**
* Introspect a component's specific state {@link Attr attribute}, returning a
* best effort casted value or the provided default if the attribute doesn't make
* sense for that particular component.
*
* @param key a {@link Attr} to resolve the value within the context
* @param defaultValue a fallback value if key doesn't resolve (also defining the return type)
*
* @return an eventual value or the default passed
*/
default <T> T scanOrDefault(Attr key, T defaultValue) {
@SuppressWarnings("unchecked")
T v = (T) scan(key);
if (v == null) {
return defaultValue;
}
return v;
}
}