/*
* Copyright 2015-2016 Red Hat, Inc. and/or its affiliates
* and other contributors as indicated by the @author tags.
*
* 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 org.hawkular.inventory.base;
import java.util.List;
import org.hawkular.inventory.api.EntityNotFoundException;
import org.hawkular.inventory.api.Query;
import org.hawkular.inventory.api.ResultFilter;
import org.hawkular.inventory.api.model.AbstractElement;
import org.hawkular.inventory.paths.SegmentType;
/**
* A base class for all the inventory traversal interfaces. Contains only a minimal set of helper methods and holds the
* traversal context.
*
* @author Lukas Krejci
* @since 0.1.0
*/
public abstract class Traversal<BE, E extends AbstractElement<?, ?>> {
protected final TraversalContext<BE, E> context;
protected Traversal(TraversalContext<BE, E> context) {
this.context = context;
}
/**
* If the inventory configuration provided a {@link ResultFilter}, this calls it to tell whether provided element
* is applicable. If the result filter is not provided by the configuration, true will always be returned.
*
* @param result the potential result to be checked for applicability in the result set
* @return true or false (!!!)
*/
protected boolean isApplicable(AbstractElement<?, ?> result) {
ResultFilter filter = context.configuration.getResultFilter();
return filter == null || filter.isApplicable(result);
}
/**
* A helper method to retrieve a single result from the query or throw an exception if the query yields no results.
*
* @param query the query to run
* @param entityType the expected type of the entity (used only for error reporting)
* @return the single result
* @throws EntityNotFoundException if the query doesn't return any results
*/
protected BE getSingle(Query query, SegmentType entityType) {
return inTx(tx -> Util.getSingle(context.discriminator(), tx, query, entityType));
}
/**
* Runs the payload in transaction. It is the payload's responsibility to commit the transaction at some point
* during its execution. If the payload throws an exception the transaction is automatically rolled back and
* the exception rethrown.
* <p>
* <p><b>WARNING:</b> the payload might be called multiple times if the transaction it runs within fails. It is
* therefore dangerous to keep any mutable state outside of the payload function that the function depends on.
*
* @param payload the payload to execute in transaction
* @param <R> the return type
* @return the return value provided by the payload
*/
protected <R> R inTx(TransactionPayload<R, BE> payload) {
return inTx(context, payload);
}
/**
* Identical to {@link #inTx(TransactionPayload)} but also returns the notifications emitted from the transaction.
* The list of notifications is final and they have already been sent. The caller should NOT send them again.
*
* @param payload the payload to run within a transaction
* @param <R> the type of the result returned from the payload
* @return the result of the payload together with the notifications sent as a result of the transaction
*/
protected <R> ResultWithNofifications<R, BE> inTxWithNotifications(TransactionPayload<R, BE> payload) {
return inCommittableTxWithNotifications(context, TransactionPayload.Committing.committing(payload));
}
protected static <R, BE, E extends AbstractElement<?, ?>>
R inTx(TraversalContext<BE, E> context, TransactionPayload<R, BE> payload) {
return inCommittableTx(context, TransactionPayload.Committing.committing(payload));
}
protected <R> R inCommittableTx(TransactionPayload.Committing<R, BE> payload) {
return inCommittableTx(context, payload);
}
protected static <R, BE, E extends AbstractElement<?, ?>>
R inCommittableTx(TraversalContext<BE, E> context, TransactionPayload.Committing<R, BE> payload) {
return Util.inCommittableTx(context, tx -> {
R v = payload.run(tx);
//k, now the transaction finished, we can send out the notifications
tx.getPreCommit().getFinalNotifications().forEach(context::notifyAll);
return v;
});
}
protected static <R, BE, E extends AbstractElement<?, ?>>
ResultWithNofifications<R, BE> inCommittableTxWithNotifications(TraversalContext<BE, E> context,
TransactionPayload.Committing<R, BE> payload) {
return Util.inCommittableTx(context, tx -> {
R v = payload.run(tx);
List<EntityAndPendingNotifications<BE, ?>> notifs = tx.getPreCommit().getFinalNotifications();
//k, now the transaction finished, we can send out the notifications
notifs.forEach(context::notifyAll);
return new ResultWithNofifications<>(v, notifs);
});
}
protected static final class ResultWithNofifications<R, BE> {
private final R result;
private final List<EntityAndPendingNotifications<BE, ?>> sentNotifications;
private ResultWithNofifications(R result, List<EntityAndPendingNotifications<BE, ?>> sentNotifications) {
this.result = result;
this.sentNotifications = sentNotifications;
}
public R getResult() {
return result;
}
public List<EntityAndPendingNotifications<BE, ?>> getSentNotifications() {
return sentNotifications;
}
}
}