/*
* 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.function.Function;
import org.hawkular.inventory.api.EntityNotFoundException;
import org.hawkular.inventory.api.Query;
import org.hawkular.inventory.api.RelationNotFoundException;
import org.hawkular.inventory.api.ResolvableToMany;
import org.hawkular.inventory.api.ResolvableToSingle;
import org.hawkular.inventory.api.model.AbstractElement;
import org.hawkular.inventory.api.model.Entity;
import org.hawkular.inventory.api.paging.Page;
import org.hawkular.inventory.api.paging.Pager;
import org.hawkular.inventory.api.paging.TransformingPage;
import org.hawkular.inventory.base.spi.CommitFailureException;
/**
* A base class for all interface impls that need to resolve the entities.
*
* @author Lukas Krejci
* @since 0.1.0
*/
abstract class Fetcher<BE, E extends AbstractElement<?, U>, U extends AbstractElement.Update>
extends Traversal<BE, E> implements ResolvableToSingle<E, U>, ResolvableToMany<E> {
private boolean useCachedEntity = true;
public Fetcher(TraversalContext<BE, E> context) {
super(context);
}
@SuppressWarnings("unchecked")
@Override
public E entity() throws EntityNotFoundException, RelationNotFoundException {
if (useCachedEntity && context.getCreatedEntity() != null) {
useCachedEntity = false;
return context.getCreatedEntity();
}
return loadEntity((b, e, tx) -> e);
}
/**
* Loads the entity from the backend and let's the caller do some conversion on either the backend representation of
* the entity or the converted entity (both of these are required even during the loading so no unnecessary work is
* done by providing both of the to the caller).
*
* @param conversion the conversion function taking the backend entity as well as the model entity
* @param <T> the result type
* @return the converted result of loading the entity
* @throws EntityNotFoundException
* @throws RelationNotFoundException
*/
protected <T> T loadEntity(EntityConvertor<BE, E, T> conversion)
throws EntityNotFoundException, RelationNotFoundException {
return inTx(tx -> {
BE result = tx.querySingle(context.discriminator(), context.select().get());
if (result == null) {
throwNotFoundException();
}
E entity = tx.convert(context.discriminator(), result, context.entityClass);
if (!isApplicable(entity)) {
throwNotFoundException();
}
return conversion.convert(result, entity, tx);
});
}
@Override
public void delete() {
inTx(tx -> {
Util.delete(context.discriminator(), context.entityClass, tx, context.select().get(), this::preDelete,
this::postDelete, false);
return null;
});
useCachedEntity = false;
context.setCreatedEntity(null);
}
@Override public void eradicate() {
inTx(tx -> {
Util.delete(context.discriminator(), context.entityClass, tx, context.select().get(), this::preDelete,
this::postDelete, true);
return null;
});
useCachedEntity = false;
context.setCreatedEntity(null);
}
@Override
public void update(U u) throws EntityNotFoundException, RelationNotFoundException {
inTx(tx -> {
Util.update(context.discriminator(), context.entityClass, tx, context.select().get(), u, this::preUpdate, this::postUpdate
);
return null;
});
if (useCachedEntity && context.getCreatedEntity() != null) {
context.setCreatedEntity((E) context.getCreatedEntity().update().with(u));
}
}
/**
* Serves the same purpose as {@link Mutator#preDelete(Object, Object, Transaction <BE>)} and is
* called during the {@link #delete()} method inside the transaction.
*
* @param deletedEntity the backend representation of the deleted entity
* @param transaction the transaction in which the delete is executing
*/
protected void preDelete(BE deletedEntity, Transaction<BE> transaction) {
}
protected void postDelete(BE deletedEntity, Transaction<BE> transaction) {
}
/**
* Hook to be run prior to update. Serves the same purpose as
* {@link Mutator#preUpdate(Object, Object, Entity.Update, Transaction <BE>)} but is not supplied
* the id object that can be determined from the updated entity.
*
* <p>By default, this does nothing.
*
* @param updatedEntity the backend representation of the updated entity
* @param update the update object
* @param transaction the transaction in which the update is executing
*/
protected void preUpdate(BE updatedEntity, U update, Transaction<BE> transaction) {
}
/**
* Hook to be run just after an update to the entity was made but before the transaction has been committed.
* This is for occasions where it is easier to read the already updated data from the backend rather than seeing
* the unmodified original data and having the update object at hand.
*
* @param updatedEntity the entity to which the update has been applied
* @param transaction the transaction in which the update is executing
*/
protected void postUpdate(BE updatedEntity, Transaction<BE> transaction) {
}
@Override
public Page<E> entities(Pager pager) {
return loadEntities(pager, (b, e, tx) -> e);
}
/**
* Loads the entities given the pager and converts them using the provided conversion function to the desired type.
*
* <p>Note that the conversion function accepts both the backend entity and the converted entity because both
* of them are required anyway during the loading and thus the function can choose which one to use without any
* additional conversion cost.
*
* @param pager the pager specifying the page of the data to load
* @param conversionFunction the conversion function to convert to the desired target type
* @param <T> the desired target type of the elements of the returned page
* @return the page of the results as specified by the pager
*/
protected <T> Page<T> loadEntities(Pager pager, EntityConvertor<BE, E, T> conversionFunction) {
return inCommittableTx(tx -> {
Function<BE, Pair<BE, E>> conversion =
(e) -> new Pair<>(e, tx.convert(context.discriminator(), e, context.entityClass));
Function<Pair<BE, E>, Boolean> filter = context.configuration.getResultFilter() == null ? null :
(p) -> context.configuration.getResultFilter().isApplicable(p.second);
Page<Pair<BE, E>> intermediate =
tx.<Pair<BE, E>>query(context.discriminator(), context.select().get(), pager, conversion, filter);
return new TransformingPage<Pair<BE, E>, T>(intermediate,
(p) -> conversionFunction.convert(p.first, p.second, tx)) {
@Override public void close() {
try {
tx.commit();
} catch (CommitFailureException e) {
throw new IllegalStateException("Failed to commit the read operation.", e);
}
super.close();
}
};
});
}
@SuppressWarnings("unchecked")
protected void throwNotFoundException() {
throwNotFoundException(context);
}
static void throwNotFoundException(TraversalContext<?, ?> context) {
if (Entity.class.isAssignableFrom(context.entityClass)) {
throw new EntityNotFoundException(context.entityClass, Query.filters(context.select().get()));
} else {
//TODO this is not correct?
throw new RelationNotFoundException((String) null, Query.filters(context.sourcePath));
}
}
private static final class Pair<F, S> {
private final F first;
private final S second;
public Pair(F first, S second) {
this.first = first;
this.second = second;
}
}
interface EntityConvertor<BE, E extends AbstractElement<?, ?>, T> {
T convert(BE backendRepresentation, E entity, Transaction<BE> tx);
}
}