blob: 168a55ca57fb89925e0173aeb9ff5e9a8b7983d0 [file] [log] [blame]
/*
* 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.polygene.api.unitofwork;
import java.time.Instant;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.function.Function;
import java.util.stream.Stream;
import org.apache.polygene.api.association.AssociationDescriptor;
import org.apache.polygene.api.association.ManyAssociation;
import org.apache.polygene.api.association.NamedAssociation;
import org.apache.polygene.api.common.Optional;
import org.apache.polygene.api.composite.AmbiguousTypeException;
import org.apache.polygene.api.entity.EntityBuilder;
import org.apache.polygene.api.entity.EntityReference;
import org.apache.polygene.api.entity.LifecycleException;
import org.apache.polygene.api.identity.HasIdentity;
import org.apache.polygene.api.identity.Identity;
import org.apache.polygene.api.property.PropertyDescriptor;
import org.apache.polygene.api.query.Query;
import org.apache.polygene.api.query.QueryBuilder;
import org.apache.polygene.api.structure.MetaInfoHolder;
import org.apache.polygene.api.structure.ModuleDescriptor;
import org.apache.polygene.api.usecase.Usecase;
/**
* All operations on entities goes through an UnitOfWork.
* <p>
* A UnitOfWork allows you to access
* Entities and work with them. All modifications to Entities are recorded by the UnitOfWork,
* and at the end they may be sent to the underlying EntityStore by calling complete(). If the
* UoW was read-only you may instead simply discard() it.
* </p>
* <p>
* A UoW differs from a traditional Transaction in the sense that it is not tied at all to the underlying
* storage resource. Because of this there is no timeout on a UoW. It can be very short or very long.
* Another difference is that if a call to complete() fails, and the cause is validation errors in the
* Entities of the UoW, then these can be corrected and the UoW retried. By contrast, when a Transaction
* commit fails, then the whole transaction has to be done from the beginning again.
* </p>
* <p>
* A UoW can be associated with a Usecase. A Usecase describes the metainformation about the process
* to be performed by the UoW.
* </p>
* <p>
* If a code block that uses a UoW throws an exception you need to ensure that this is handled properly,
* and that the UoW is closed before returning. Because discard() is a no-op if the UoW is closed, we therefore
* recommend the following template to be used:
* </p>
* <pre>
* UnitOfWork uow = module.newUnitOfWork();
* try
* {
* ...
* uow.complete();
* }
* finally
* {
* uow.discard();
* }
* </pre>
* <p>
* This ensures that in the happy case the UoW is completed, and if any exception is thrown the UoW is discarded. After
* the UoW has completed the discard() method doesn't do anything, and so has no effect. You can choose to either add
* catch blocks for any exceptions, including exceptions from complete(), or skip them.
* </p>
* <p>
* Since 2.1 you can leverage Java 7 Automatic Resource Management (ie. Try With Resources) and use the following
* template instead:
* </p>
* <pre>
* try( UnitOfWork uow = module.newUnitOfWork() )
* {
* ...
* uow.complete();
* }
* </pre>
* <p>
* It has the very same effect as the template above but is shorter.</p>
*/
public interface UnitOfWork extends MetaInfoHolder, AutoCloseable
{
/**
* Get the UnitOfWorkFactory that this UnitOfWork was created from.
*
* @return The UnitOfWorkFactory instance that was used to create this UnitOfWork.
*/
UnitOfWorkFactory unitOfWorkFactory();
/**
* Current Time is a relative concept in systems capable of event sourcing and other
* history-capable systems. Current time is always expected to be read from here, and
* history-capable systems will set the current time for each {@link UnitOfWorkFactory#newUnitOfWork(Instant)}
*
* @return the current time, either actual real time or historical time being part of some playback.
*/
Instant currentTime();
/**
* Get the Usecase for this UnitOfWork
*
* @return the Usecase
*/
Usecase usecase();
/**
* Sets an arbitrary metaInfo object on this {@code UnitOfWork} which can be used for application-specific
* information.
* <p>
* The metaInfo object is retrieved by the {@link UnitOfWork#metaInfo(Class)} method on the same UnitOfWork
* instance.
* </p>
*
* @param metaInfo The metaInfo object that can be retrieved with {@link UnitOfWork#metaInfo(Class)}.
*/
void setMetaInfo( Object metaInfo );
/**
* Creates a {@link Query} from the given {@link QueryBuilder} on this {@code UnitOfWork}.
*
* @param queryBuilder The QueryBuilder holding the query specification/expression
* @param <T> The resulting type of the query.
* @return A Query against this {@code UnitOfWork}
*/
<T> Query<T> newQuery( QueryBuilder<T> queryBuilder );
/**
* Create a new Entity which implements the given mixin type.
* <p>
* An EntityComposite
* will be chosen according to what has been registered and the visibility rules
* for Modules and Layers will be considered. If several
* EntityComposites implement the type then an AmbiguousTypeException will be thrown.
* </p>
* <p>
* The reference of the Entity will be generated by the IdentityGenerator of the Module of the EntityComposite.
* </p>
*
* @param <T> Entity type
* @param type the mixin type that the EntityComposite must implement
* @return a new Entity
* @throws NoSuchEntityTypeException if no EntityComposite type of the given mixin type has been registered
* @throws AmbiguousTypeException If several mixins implement the given type
* @throws LifecycleException if the entity cannot be created
*/
<T> T newEntity( Class<T> type )
throws NoSuchEntityTypeException, AmbiguousTypeException, LifecycleException;
/**
* Create a new Entity which implements the given mixin type. An EntityComposite
* will be chosen according to what has been registered and the visibility rules
* for Modules and Layers will be considered. If several
* EntityComposites implement the type then an AmbiguousTypeException will be thrown.
*
* @param <T> Entity type
* @param type the mixin type that the EntityComposite must implement
* @param identity the reference of the new Entity
* @return a new Entity
* @throws NoSuchEntityTypeException if no EntityComposite type of the given mixin type has been registered
* @throws AmbiguousTypeException If several mixins implement the given type
* @throws LifecycleException if the entity cannot be created
*/
<T> T newEntity( Class<T> type, @Optional Identity identity )
throws NoSuchEntityTypeException, AmbiguousTypeException, LifecycleException;
/**
* Create a new EntityBuilder for an EntityComposite which implements the given mixin type. An EntityComposite
* will be chosen according to what has been registered and the visibility rules
* for Modules and Layers will be considered. If several
* EntityComposites implement the type then an AmbiguousTypeException will be thrown.
*
* @param <T> Entity type
* @param type the mixin type that the EntityComposite must implement
* @return a new EntityBuilder
* @throws NoSuchEntityTypeException if no EntityComposite type of the given mixin type has been registered
* @throws AmbiguousTypeException If several mixins implement the given type
*/
<T> EntityBuilder<T> newEntityBuilder( Class<T> type )
throws NoSuchEntityTypeException, AmbiguousTypeException;
/**
* Create a new EntityBuilder for an EntityComposite which implements the given mixin type. An EntityComposite
* will be chosen according to what has been registered and the visibility rules
* for Modules and Layers will be considered. If several
* mixins implement the type then an AmbiguousTypeException will be thrown.
*
* @param <T> Entity type
* @param type the mixin type that the EntityComposite must implement
* @param identity the reference of the new Entity
* @return a new EntityBuilder
* @throws NoSuchEntityTypeException if no EntityComposite type of the given mixin type has been registered
* @throws AmbiguousTypeException If several mixins implement the given type
*/
<T> EntityBuilder<T> newEntityBuilder( Class<T> type, @Optional Identity identity )
throws NoSuchEntityTypeException, AmbiguousTypeException;
/**
* Create a new EntityBuilder for an EntityComposite wich implements the given mixin type starting with the given
* state.
* <p>
* An EntityComposite will be chosen according to what has been registered and the visibility rules for Modules and
* Layers will be considered.
*
* @param <T> Entity type
* @param type Entity type
* @param propertyFunction a function providing the state of properties
* @param associationFunction a function providing the state of associations
* @param manyAssociationFunction a function providing the state of many associations
* @param namedAssociationFunction a function providing the state of named associations
* @return a new EntityBuilder starting with the given state
* @throws NoSuchEntityTypeException if no EntityComposite type of the given mixin type has been registered
* @throws AmbiguousTypeException If several mixins implement the given type
*/
<T> EntityBuilder<T> newEntityBuilderWithState( Class<T> type,
Function<PropertyDescriptor, Object> propertyFunction,
Function<AssociationDescriptor, EntityReference> associationFunction,
Function<AssociationDescriptor, Stream<EntityReference>> manyAssociationFunction,
Function<AssociationDescriptor, Stream<Map.Entry<String, EntityReference>>> namedAssociationFunction
)
throws NoSuchEntityTypeException, AmbiguousTypeException;
/**
* Create a new EntityBuilder for an EntityComposite wich implements the given mixin type starting with the given
* state.
* <p>
* An EntityComposite will be chosen according to what has been registered and the visibility rules for Modules and
* Layers will be considered.
*
* @param <T> Entity type
* @param type Entity type
* @param identity the reference of the new Entity
* @param propertyFunction a function providing the state of properties
* @param associationFunction a function providing the state of associations
* @param manyAssociationFunction a function providing the state of many associations
* @param namedAssociationFunction a function providing the state of named associations
* @return a new EntityBuilder starting with the given state
* @throws NoSuchEntityTypeException If no mixins implements the given type
* @throws AmbiguousTypeException If several mixins implement the given type
*/
<T> EntityBuilder<T> newEntityBuilderWithState( Class<T> type, @Optional Identity identity,
Function<PropertyDescriptor, Object> propertyFunction,
Function<AssociationDescriptor, EntityReference> associationFunction,
Function<AssociationDescriptor, Stream<EntityReference>> manyAssociationFunction,
Function<AssociationDescriptor, Stream<Map.Entry<String, EntityReference>>> namedAssociationFunction
)
throws NoSuchEntityTypeException, AmbiguousTypeException;
/**
* Find an Entity of the given mixin type with the give reference. This
* method verifies that it exists by asking the underlying EntityStore.
*
* @param <T> Entity type
* @param type of the entity
* @param identity of the entity
* @return the entity
* @throws NoSuchEntityTypeException if no entity type could be found
* @throws NoSuchEntityException if the entity could not be found
*/
<T> T get( Class<T> type, Identity identity )
throws NoSuchEntityTypeException, NoSuchEntityException;
/**
* If you have a reference to an Entity from another
* UnitOfWork and want to create a reference to it in this
* UnitOfWork, then call this method.
*
* @param <T> Entity type
* @param entity the Entity to be dereferenced
* @return an Entity from this UnitOfWork
* @throws NoSuchEntityTypeException if no entity type could be found
*/
<T> T get( T entity )
throws NoSuchEntityTypeException;
/**
* Remove the given Entity.
*
* @param entity the Entity to be removed.
* @throws LifecycleException if the entity could not be removed
*/
void remove( Object entity )
throws LifecycleException;
/**
* Complete this UnitOfWork. This will send all the changes down to the underlying
* EntityStore's.
*
* @throws UnitOfWorkCompletionException if the UnitOfWork could not be completed
* @throws ConcurrentEntityModificationException if entities have been modified by others
*/
void complete()
throws UnitOfWorkCompletionException, ConcurrentEntityModificationException;
/**
* Discard this UnitOfWork. Use this if a failure occurs that you cannot handle,
* or if the usecase was of a read-only character. This is a no-op of the UnitOfWork
* is already closed.
*/
void discard();
/**
* Discard this UnitOfWork. Use this if a failure occurs that you cannot handle,
* or if the usecase was of a read-only character. This is a no-op of the UnitOfWork
* is already closed. This simply call the {@link #discard()} method and is an
* implementation of the {@link AutoCloseable} interface providing Try With Resources
* support for UnitOfWork.
*/
@Override
void close();
/**
* Check if the UnitOfWork is open. It is closed after either complete() or discard()
* methods have been called successfully.
*
* @return true if the UnitOfWork is open.
*/
boolean isOpen();
/**
* Check if the UnitOfWork is paused. It is not paused after it has been create through the
* UnitOfWorkFactory, and it can be paused by calling {@link #pause()} and then resumed by calling
* {@link #resume()}.
*
* @return true if this UnitOfWork has been paused.
*/
boolean isPaused();
/**
* Pauses this UnitOfWork.
* <p>
* Calling this method will cause the underlying UnitOfWork to become the current UnitOfWork until the
* the resume() method is called. It is the client's responsibility not to drop the reference to this
* UnitOfWork while being paused.
* </p>
*/
void pause();
/**
* Resumes this UnitOfWork to again become the current UnitOfWork.
*/
void resume();
/**
* Register a callback. Callbacks are invoked when the UnitOfWork
* is completed or discarded.
*
* @param callback a callback to be registered with this UnitOfWork
*/
void addUnitOfWorkCallback( UnitOfWorkCallback callback );
/**
* Unregister a callback. Callbacks are invoked when the UnitOfWork
* is completed or discarded.
*
* @param callback a callback to be unregistered with this UnitOfWork
*/
void removeUnitOfWorkCallback( UnitOfWorkCallback callback );
/**
* Converts the provided Entity to a Value of the same type.
* This is a convenience method to convert an EntityComposite to a ValueComposite.
* <p>
* All Property values are transferred across as-is, and the Association, ManyAssociation
* and NamedAssociatino values are kept in the ValueComposite as EntityReferences
* until they are dereferenced (get() and other methods), and IF a UnitOfWork is
* present at dereferencing the corresponding EntityCompoiste is retrieved from the
* EntityStore. If there is not an UnitOfWork present, an exception is thrown.
* </p>
* <p>
* For this to work, the Composites (both Entity and Value) must not declare the
* EntityComposite and ValueComposite super types, but rely on the declaration in
* the assembly, and also extend the Identity supertype.
* </p>
* Example;
* <pre><code>
* public interface Person extends Identity { ... };
* public class MyAssembler
* {
* public void assemble( ModuleAssembly module )
* {
* module.values( Person.class );
* module.entities( Person.class );
* }
* }
* </code></pre>
*
* @param <T> The generic shared type
* @param primaryType The shared type for which the properties and associations will
* be converted. Properties outside this type will be ignored.
* @param entityComposite The entity to be convered.
* @return The Value
*/
<T extends HasIdentity> T toValue( Class<T> primaryType, T entityComposite );
/**
* Converts all the entities referenced in the ManyAssociation into a List of values of the same type.
* <p>
* All the referenced entities inside the association will be fetched from the underlying entity store,
* which is potentially very expensive operation. Each of the fetched entities will be passed to
* {@link #toValue(Class, HasIdentity)}, and its associations will NOT be converted into values, but remain
* {@link EntityReference} values. Hence there is no problem with circular references.
* </p>
* <p>
* For this to work, the type &lt;T&gt; must be registered at bootstrap as both an Entity and a Value, and
* as seen in the method signature, also be sub-type of {@link HasIdentity}.
* </p>
*
* @param association The association of entities to be converted into values.
* @param <T> The primary type of the association.
* @return A List of ValueComposites that has been converted from EntityComposites referenced by the Associations.
* @see #toValue(Class, HasIdentity)
*/
<T extends HasIdentity> List<T> toValueList( ManyAssociation<T> association );
/**
* Converts all the entities referenced in the ManyAssociation into a Set of values of the same type.
* <p>
* All the referenced entities inside the association will be fetched from the underlying entity store,
* which is potentially very expensive operation. However, any duplicate EntityReferences in the association
* will be dropped before the fetch occurs. Each of the fetched entities will be passed to
* {@link #toValue(Class, HasIdentity)}, and its associations will NOT be converted into values, but remain
* {@link EntityReference} values. Hence there is no problem with circular references.
* </p>
* <p>
* For this to work, the type &lt;T&gt; must be registered at bootstrap as both an Entity and a Value, and
* as seen in the method signature, also be sub-type of {@link HasIdentity}.
* </p>
*
* @param association The association of entities to be converted into values.
* @param <T> The primary type of the association.
* @return A List of ValueComposites that has been converted from EntityComposites referenced by the Associations.
* @see #toValue(Class, HasIdentity)
*/
<T extends HasIdentity> Set<T> toValueSet( ManyAssociation<T> association );
/**
* Converts the {@link NamedAssociation} into a Map with a String key and a ValueComposite as the value.
* <p>
* A {@link NamedAssociation} is effectively a Map with a String key and an EntityReference as the value. The
* EntityReference is fetched from the entity store and converted into a value of the same type.Each of the fetched
* entities will be passed to {@link #toValue(Class, HasIdentity)}, and its associations will NOT be converted into
* values, but remain {@link EntityReference} values. Hence there is no problem with circular references.
* </p>
* <p>
* For this to work, the type &lt;T&gt; must be registered at bootstrap as both an Entity and a Value, and
* as seen in the method signature, also be sub-type of {@link HasIdentity}.
* </p>
*
* @param association The association of entities to be converted into values.
* @param <T> The primary type of the association.
* @return A List of ValueComposites that has been converted from EntityComposites referenced by the Associations.
* @see #toValue(Class, HasIdentity)
*/
<T extends HasIdentity> Map<String, T> toValueMap( NamedAssociation<T> association );
/**
* Converts the provided Value to an Entity of the same type.
* This is a convenience method to convert a ValueComposite to an EntityComposite.
* <p>
* All Property values are transferred across as-is (no deep copy in case mutable
* types (DISCOURAGED!) are used), and the Association, ManyAssociation
* and NamedAssociatino that were in the ValueComposite as EntityReferences are
* transferred into the EntityComposite correctly, and can be dereferenced.
* </p>
* <p>
* This method MUST be called within a UnitOfWork.
* </p>
* <p>
* If an Entity with the Identity in the ValueComposite already exists, then that
* Entity is updated with the values from the ValueComposite. If an Entity of
* that Identity doesn't exist a new one is created.
* </p>
* <p>
* For this to work, the Composites (both Entity and Value) must not declare the
* EntityComposite and ValueComposite super types, but rely on the declaration in
* the assembly, and also extend the Identity supertype.
* </p>
* Example;
* <pre><code>
* public interface Person extends Identity { ... };
* public class MyAssembler
* {
* public void assemble( ModuleAssembly module )
* {
* module.values( Person.class );
* module.entities( Person.class );
* }
* }
* </code></pre>
*
* @param <T> The generic shared type
* @param primaryType The shared type for which the properties and associations will
* be converted. Properties outside this type will be ignored.
* @param valueComposite The Value to be convered into an Entity.
* @return The new or updated Entity
*/
<T extends HasIdentity> T toEntity( Class<T> primaryType, T valueComposite );
/**
* The Module of the UnitOfWork is defined as the Module the UnitOfWorkFactory belonged to from where the
* UnitOfWork was created.
*
* @return the Module where this UnitOfWork was initialized.
*/
ModuleDescriptor module();
}