/*
* 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.feed.function;
import static com.google.common.base.Preconditions.checkNotNull;
import java.util.List;
import java.util.Set;
import java.util.concurrent.Callable;
import java.util.concurrent.TimeUnit;
import org.apache.brooklyn.api.entity.EntityLocal;
import org.apache.brooklyn.config.ConfigKey;
import org.apache.brooklyn.core.config.ConfigKeys;
import org.apache.brooklyn.core.feed.AbstractFeed;
import org.apache.brooklyn.core.feed.AttributePollHandler;
import org.apache.brooklyn.core.feed.DelegatingPollHandler;
import org.apache.brooklyn.util.time.Duration;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.google.common.base.Objects;
import com.google.common.collect.HashMultimap;
import com.google.common.collect.Lists;
import com.google.common.collect.SetMultimap;
import com.google.common.collect.Sets;
import com.google.common.reflect.TypeToken;
/**
* Provides a feed of attribute values, by periodically invoking functions.
*
* Example usage (e.g. in an entity that extends SoftwareProcessImpl):
* <pre>
* {@code
* private FunctionFeed feed;
*
* //@Override
* protected void connectSensors() {
* super.connectSensors();
*
* feed = FunctionFeed.builder()
* .entity(this)
* .poll(new FunctionPollConfig<Object, Boolean>(SERVICE_UP)
* .period(500, TimeUnit.MILLISECONDS)
* .callable(new Callable<Boolean>() {
* public Boolean call() throws Exception {
* return getDriver().isRunning();
* }
* })
* .onExceptionOrFailure(Functions.constant(Boolan.FALSE))
* .build();
* }
*
* {@literal @}Override
* protected void disconnectSensors() {
* super.disconnectSensors();
* if (feed != null) feed.stop();
* }
* }
* </pre>
*
* @author aled
*/
public class FunctionFeed extends AbstractFeed {
private static final Logger log = LoggerFactory.getLogger(FunctionFeed.class);
// Treat as immutable once built
@SuppressWarnings("serial")
public static final ConfigKey<SetMultimap<FunctionPollIdentifier, FunctionPollConfig<?,?>>> POLLS = ConfigKeys.newConfigKey(
new TypeToken<SetMultimap<FunctionPollIdentifier, FunctionPollConfig<?,?>>>() {},
"polls");
public static Builder builder() {
return new Builder();
}
public static Builder builder(String uniqueTag) {
return new Builder().uniqueTag(uniqueTag);
}
public static class Builder {
private EntityLocal entity;
private boolean onlyIfServiceUp = false;
private long period = 500;
private TimeUnit periodUnits = TimeUnit.MILLISECONDS;
private List<FunctionPollConfig<?,?>> polls = Lists.newArrayList();
private String uniqueTag;
private volatile boolean built;
public Builder entity(EntityLocal val) {
this.entity = val;
return this;
}
public Builder onlyIfServiceUp() { return onlyIfServiceUp(true); }
public Builder onlyIfServiceUp(boolean onlyIfServiceUp) {
this.onlyIfServiceUp = onlyIfServiceUp;
return this;
}
public Builder period(Duration d) {
return period(d.toMilliseconds(), TimeUnit.MILLISECONDS);
}
public Builder period(long millis) {
return period(millis, TimeUnit.MILLISECONDS);
}
public Builder period(long val, TimeUnit units) {
this.period = val;
this.periodUnits = units;
return this;
}
public Builder poll(FunctionPollConfig<?,?> config) {
polls.add(config);
return this;
}
public Builder uniqueTag(String uniqueTag) {
this.uniqueTag = uniqueTag;
return this;
}
public FunctionFeed build() {
built = true;
FunctionFeed result = new FunctionFeed(this);
result.setEntity(checkNotNull(entity, "entity"));
result.start();
return result;
}
@Override
protected void finalize() {
if (!built) log.warn("FunctionFeed.Builder created, but build() never called");
}
}
private static class FunctionPollIdentifier {
final Callable<?> job;
private FunctionPollIdentifier(Callable<?> job) {
this.job = checkNotNull(job, "job");
}
@Override
public int hashCode() {
return Objects.hashCode(job);
}
@Override
public boolean equals(Object other) {
return (other instanceof FunctionPollIdentifier) && Objects.equal(job, ((FunctionPollIdentifier)other).job);
}
}
/**
* For rebind; do not call directly; use builder
*/
public FunctionFeed() {
}
protected FunctionFeed(Builder builder) {
setConfig(ONLY_IF_SERVICE_UP, builder.onlyIfServiceUp);
SetMultimap<FunctionPollIdentifier, FunctionPollConfig<?,?>> polls = HashMultimap.<FunctionPollIdentifier,FunctionPollConfig<?,?>>create();
for (FunctionPollConfig<?,?> config : builder.polls) {
if (!config.isEnabled()) continue;
@SuppressWarnings({ "rawtypes", "unchecked" })
FunctionPollConfig<?,?> configCopy = new FunctionPollConfig(config);
if (configCopy.getPeriod() < 0) configCopy.period(builder.period, builder.periodUnits);
Callable<?> job = config.getCallable();
polls.put(new FunctionPollIdentifier(job), configCopy);
}
setConfig(POLLS, polls);
initUniqueTag(builder.uniqueTag, polls.values());
}
@SuppressWarnings({ "unchecked", "rawtypes" })
@Override
protected void preStart() {
SetMultimap<FunctionPollIdentifier, FunctionPollConfig<?, ?>> polls = getConfig(POLLS);
for (final FunctionPollIdentifier pollInfo : polls.keySet()) {
Set<FunctionPollConfig<?,?>> configs = polls.get(pollInfo);
long minPeriod = Integer.MAX_VALUE;
Set<AttributePollHandler<?>> handlers = Sets.newLinkedHashSet();
for (FunctionPollConfig<?,?> config : configs) {
handlers.add(new AttributePollHandler(config, entity, this));
if (config.getPeriod() > 0) minPeriod = Math.min(minPeriod, config.getPeriod());
}
getPoller().scheduleAtFixedRate(
(Callable)pollInfo.job,
new DelegatingPollHandler(handlers),
minPeriod);
}
}
}