/*
* 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.io.InputStream;
import java.time.Instant;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.function.Consumer;
import java.util.function.Function;
import org.hawkular.inventory.api.Inventory;
import org.hawkular.inventory.api.Query;
import org.hawkular.inventory.api.Relationships;
import org.hawkular.inventory.api.model.AbstractElement;
import org.hawkular.inventory.api.model.Blueprint;
import org.hawkular.inventory.api.model.Entity;
import org.hawkular.inventory.api.model.Hashes;
import org.hawkular.inventory.api.model.StructuredData;
import org.hawkular.inventory.api.paging.Page;
import org.hawkular.inventory.api.paging.Pager;
import org.hawkular.inventory.base.spi.CommitFailureException;
import org.hawkular.inventory.base.spi.Discriminator;
import org.hawkular.inventory.base.spi.ElementNotFoundException;
import org.hawkular.inventory.base.spi.EntityHistory;
import org.hawkular.inventory.base.spi.InventoryBackend;
import org.hawkular.inventory.paths.CanonicalPath;
import org.hawkular.inventory.paths.RelativePath;
/**
* A transaction is essentially a "window" into the backend for transactional payloads executed using the
* {@link TransactionPayload}s.
* <p>
* It is almost the same as the {@link InventoryBackend} interface, but doesn't contain the methods for direct
* transaction manipulation. This is because by default, transactional payloads don't commit - the commit is handled
* automatically by the base impl. Also, nested transactions are not supported so the
* {@link InventoryBackend#startTransaction()} is not exposed through this interface.
*
* @author Lukas Krejci
* @since 0.13.0
*/
public interface Transaction<E> {
/**
* This is very dangerous, do NOT obtain the raw inventory backend unless you have utterly serious reasons to do so.
* All the backend methods are delegated to from this transaction, unless they are transaction-handling related.
* <p>
* You should not try to do your own transaction handling. If you do, the code should change to accomodate for your
* usecase in a non-exceptional way.
*
* @return the inventory backend that this transaction delegates to
*/
InventoryBackend<E> directAccess();
/**
* By default this does nothing, but is used during execution of a transactio frame to keep track of the
* individual payloads executed as part of the transaction frame.
* <p>
* These are then used in case of commit failure for a re-play.
*
* @param committedPayload a payload that has just been committed.
*/
void registerCommittedPayload(TransactionPayload.Committing<?, E> committedPayload);
PreCommit<E> getPreCommit();
<T> T convert(Discriminator discriminator, E entityRepresentation, Class<T> entityType);
void markDeleted(Discriminator discriminator, E entity);
void deleteStructuredData(E dataRepresentation);
E descendToData(Discriminator discriminator, E dataEntityRepresentation, RelativePath dataPath);
void eradicate(E entityRepresentation);
CanonicalPath extractCanonicalPath(E entityRepresentation);
String extractId(E entityRepresentation);
String extractIdentityHash(Discriminator discriminator, E entityRepresentation);
String extractContentHash(Discriminator discriminator, E entityRepresentation);
String extractSyncHash(Discriminator discriminator, E entityRepresentation);
String extractRelationshipName(E relationship);
Class<?> extractType(E entityRepresentation);
E find(Discriminator discriminator, CanonicalPath element) throws ElementNotFoundException;
InputStream getGraphSON(Discriminator discriminator, String tenantId);
E getRelationship(Discriminator discriminator, E source, E target, String relationshipName) throws ElementNotFoundException;
Set<E> getRelationships(Discriminator discriminator, E entity, Relationships.Direction direction,
String... names);
E getRelationshipSource(Discriminator discriminator, E relationship);
E getRelationshipTarget(Discriminator discriminator, E relationship);
<T extends Entity<?, ?>> Iterator<T> getTransitiveClosureOver(
Discriminator discriminator, CanonicalPath startingPoint,
Relationships.Direction direction, Class<T> clazz,
String... relationshipNames);
Iterator<E> getTransitiveClosureOver(Discriminator discriminator, E startingPoint,
Relationships.Direction direction,
String... relationshipNames);
boolean hasRelationship(Discriminator discriminator, E entity, Relationships.Direction direction,
String relationshipName);
boolean hasRelationship(Discriminator discriminator, E source, E target, String relationshipName);
boolean isBackendInternal(E element);
boolean isUniqueIndexSupported();
E persist(Discriminator discriminator, CanonicalPath path,
Blueprint blueprint);
E persist(StructuredData structuredData);
Page<E> query(Discriminator discriminator, Query query,
Pager pager);
<T> Page<T> query(Discriminator discriminator, Query query,
Pager pager,
Function<E, T> conversion,
Function<T, Boolean> filter);
E querySingle(Discriminator discriminator, Query query);
E relate(Discriminator discriminator, E sourceEntity, E targetEntity, String name,
Map<String, Object> properties);
Page<E> traverse(Discriminator discriminator, E startingPoint, Query query,
Pager pager);
E traverseToSingle(Discriminator discriminator, E startingPoint, Query query);
void update(Discriminator discriminator, E entity, AbstractElement.Update update);
void updateHashes(Discriminator discriminator, E entity, Hashes hashes);
/**
* Checks the exception thrown during the commit and returns true if the backend requires explicit rollback after
* such failure occured or false if the failure caused the transaction to close itself automatically.
*
* @param t the exception thrown during commit
* @return true to explictly roll back or false if the exception already caused the transaction to close
*/
default boolean requiresRollbackAfterFailure(Throwable t) {
return true;
}
<T extends Entity<?, U>, U extends Entity.Update>
EntityHistory<T> getHistory(E entity, Class<T> entityType, Instant from, Instant to);
interface PreCommit<E> {
/**
* This is always to be called AFTER a transaction is committed and therefore after {@link #getActions()} is
* called.
* @return the notifications to send out - these can be different from what was originally requested by the base
* code
*/
List<EntityAndPendingNotifications<E, ?>> getFinalNotifications();
/**
* Initializes this pre-commit using the inventory and/or the backend.
* @param inventory the inventory to use, it is bound to the provided transaction
* @param tx the transaction for which this pre-commit is defined
*/
void initialize(Inventory inventory, Transaction<E> tx);
/**
* Resets all the internal structures as to the initialized state.
*/
void reset();
/**
* Adds an explicit action to be run prior to commit.
*
* @param action the action
*/
void addAction(Consumer<Transaction<E>> action);
/**
* @return the list of actions to run prior to commit. This is a super set of the actions explicitly added by
* the {@link #addAction(Consumer)} method.
*/
List<Consumer<Transaction<E>>> getActions();
/**
* Adds a notification to be sent out after the commit is made. This is analyzed and can result in new
* actions being run prior to commit.
*
* @param element the changed element and its notifications.
*/
void addNotifications(EntityAndPendingNotifications<E, ?> element);
/**
* Similar to {@link #addNotifications(EntityAndPendingNotifications)} but the provided element and its
* notifications are not subject to further analysis.
* <p>
* This is used only in special circumstances, like passing notifications from one pre-commit to another
* while handling "silent" transactions that are used in {@link org.hawkular.inventory.api.TransactionFrame}s
* or when inventory is told to {@link BaseInventory#keepTransaction(Transaction)}.
*
* @param element the changed element and its notifications
*/
void addProcessedNotifications(EntityAndPendingNotifications<E, ?> element);
class Simple<E> implements PreCommit<E> {
private List<EntityAndPendingNotifications<E, ?>> notifs = new ArrayList<>();
private List<Consumer<Transaction<E>>> actions = new ArrayList<>();
protected Transaction<E> transaction;
protected Inventory inventory;
@Override public void initialize(Inventory inventory, Transaction<E> tx) {
this.inventory = inventory;
this.transaction = tx;
}
@Override public void reset() {
notifs.clear();
actions.clear();
}
@Override public void addNotifications(EntityAndPendingNotifications<E, ?> element) {
notifs.add(element);
}
@Override public void addProcessedNotifications(EntityAndPendingNotifications<E, ?> element) {
notifs.add(element);
}
@Override public void addAction(Consumer<Transaction<E>> action) {
actions.add(action);
}
@Override public List<Consumer<Transaction<E>>> getActions() {
return actions;
}
@Override public List<EntityAndPendingNotifications<E, ?>> getFinalNotifications() {
return notifs;
}
}
}
/**
* This is to be used only in those extreme situations when one needs a manual control over when a payload is
* committed.
*
* @param <E>
*/
class Committable<E> extends DelegatingTransaction<E> implements Transaction<E> {
/**
* Converts the provided transaction to a committable one.
* <p>
* <b>WARNING:</b> DO NOT USE THE PROVIDED TRANSACTION FOR ANYTHING AFTER THIS CALL.
*
* @param tx the transaction
* @param <E> the type of the elements that the backend deals with
* @return a commitable transaction to be used instead of the provided one from this point on.
*/
public static <E> Committable<E> from(Transaction<E> tx) {
return new Committable<>(tx);
}
protected Committable(Transaction<E> tx) {
super(tx);
}
public void commit() throws CommitFailureException {
getPreCommit().getActions().forEach(a -> a.accept(this));
directAccess().commit();
}
public void rollback() {
directAccess().rollback();
}
}
}