blob: 071d2c3b8abc467bcc62c04e12a556cf4c49c7d7 [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.runtime.unitofwork;
import java.time.Instant;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import java.util.function.Function;
import java.util.function.Predicate;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import org.apache.polygene.api.PolygeneAPI;
import org.apache.polygene.api.association.AssociationDescriptor;
import org.apache.polygene.api.association.AssociationStateHolder;
import org.apache.polygene.api.association.ManyAssociation;
import org.apache.polygene.api.association.NamedAssociation;
import org.apache.polygene.api.composite.Composite;
import org.apache.polygene.api.entity.EntityBuilder;
import org.apache.polygene.api.entity.EntityComposite;
import org.apache.polygene.api.entity.EntityDescriptor;
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.identity.IdentityGenerator;
import org.apache.polygene.api.identity.StringIdentity;
import org.apache.polygene.api.injection.scope.Service;
import org.apache.polygene.api.injection.scope.Structure;
import org.apache.polygene.api.injection.scope.Uses;
import org.apache.polygene.api.property.Property;
import org.apache.polygene.api.property.PropertyDescriptor;
import org.apache.polygene.api.property.StateHolder;
import org.apache.polygene.api.query.Query;
import org.apache.polygene.api.query.QueryBuilder;
import org.apache.polygene.api.query.QueryExecutionException;
import org.apache.polygene.api.query.grammar.OrderBy;
import org.apache.polygene.api.structure.ModuleDescriptor;
import org.apache.polygene.api.unitofwork.ConcurrentEntityModificationException;
import org.apache.polygene.api.unitofwork.NoSuchEntityException;
import org.apache.polygene.api.unitofwork.NoSuchEntityTypeException;
import org.apache.polygene.api.unitofwork.ToEntityConverter;
import org.apache.polygene.api.unitofwork.ToValueConverter;
import org.apache.polygene.api.unitofwork.UnitOfWork;
import org.apache.polygene.api.unitofwork.UnitOfWorkCallback;
import org.apache.polygene.api.unitofwork.UnitOfWorkCompletionException;
import org.apache.polygene.api.unitofwork.UnitOfWorkFactory;
import org.apache.polygene.api.usecase.Usecase;
import org.apache.polygene.api.value.ValueBuilder;
import org.apache.polygene.runtime.association.AssociationInstance;
import org.apache.polygene.runtime.composite.FunctionStateResolver;
import org.apache.polygene.runtime.entity.EntityInstance;
import org.apache.polygene.runtime.entity.EntityModel;
import org.apache.polygene.runtime.property.PropertyModel;
import org.apache.polygene.runtime.value.ValueStateInstance;
import org.apache.polygene.spi.entity.EntityState;
import org.apache.polygene.spi.entity.EntityStatus;
import org.apache.polygene.spi.entitystore.EntityStore;
import org.apache.polygene.spi.module.ModuleSpi;
import org.apache.polygene.spi.query.EntityFinder;
import org.apache.polygene.spi.query.EntityFinderException;
import org.apache.polygene.spi.query.QueryBuilderSPI;
import org.apache.polygene.spi.query.QuerySource;
import static org.apache.polygene.api.composite.CompositeInstance.compositeInstanceOf;
import static org.apache.polygene.api.identity.HasIdentity.IDENTITY_STATE_NAME;
/**
* JAVADOC
*/
public class ModuleUnitOfWork
implements UnitOfWork
{
@Uses
private UnitOfWorkInstance uow;
@Structure
private PolygeneAPI api;
@Structure
private ModuleDescriptor module;
@Service
private UnitOfWorkFactory unitOfWorkFactory;
public ModuleDescriptor module()
{
return module;
}
public UnitOfWorkInstance instance()
{
return uow;
}
@Override
public UnitOfWorkFactory unitOfWorkFactory()
{
return unitOfWorkFactory;
}
@Override
public Instant currentTime()
{
return uow.currentTime();
}
@Override
public Usecase usecase()
{
return uow.usecase();
}
@Override
public <T> T metaInfo( Class<T> infoType )
{
return uow.metaInfo().get( infoType );
}
@Override
public void setMetaInfo( Object metaInfo )
{
uow.metaInfo().set( metaInfo );
}
@Override
@SuppressWarnings( { "raw", "unchecked" } )
public <T> Query<T> newQuery( QueryBuilder<T> queryBuilder )
{
QueryBuilderSPI queryBuilderSPI = (QueryBuilderSPI) queryBuilder;
return queryBuilderSPI.newQuery( new UoWQuerySource( this ) );
}
@Override
public <T> T newEntity( Class<T> type )
throws NoSuchEntityTypeException, LifecycleException
{
return newEntity( type, null );
}
@Override
public <T> T newEntity( Class<T> type, Identity identity )
throws NoSuchEntityTypeException, LifecycleException
{
return newEntityBuilder( type, identity ).newInstance();
}
@Override
public <T> EntityBuilder<T> newEntityBuilder( Class<T> type )
throws NoSuchEntityTypeException
{
return newEntityBuilder( type, null );
}
@Override
public <T> EntityBuilder<T> newEntityBuilder( Class<T> type, Identity identity )
throws NoSuchEntityTypeException
{
EntityDescriptor model = module.typeLookup().lookupEntityModel( type );
if( model == null )
{
throw new NoSuchEntityTypeException( type.getName(), module.name(), module.typeLookup() );
}
ModuleDescriptor modelModule = model.module();
EntityStore entityStore = ( (ModuleSpi) modelModule.instance() ).entityStore();
// Generate id if necessary
if( identity == null )
{
IdentityGenerator idGen = ( (ModuleSpi) modelModule.instance() ).identityGenerator();
identity = idGen.generate( model.types().findFirst().orElse( null ) );
}
EntityBuilder<T> builder;
builder = new EntityBuilderInstance<>( model,
this,
uow.getEntityStoreUnitOfWork( entityStore ),
identity );
return builder;
}
@Override
public <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
{
return newEntityBuilderWithState( type, null,
propertyFunction,
associationFunction,
manyAssociationFunction,
namedAssociationFunction );
}
@Override
public <T> EntityBuilder<T> newEntityBuilderWithState(
Class<T> type, 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
{
Objects.requireNonNull( propertyFunction, "propertyFunction" );
Objects.requireNonNull( associationFunction, "associationFunction" );
Objects.requireNonNull( manyAssociationFunction, "manyAssociationFunction" );
Objects.requireNonNull( namedAssociationFunction, "namedAssociationFunction" );
EntityDescriptor model = module.typeLookup().lookupEntityModel( type );
if( model == null )
{
throw new NoSuchEntityTypeException( type.getName(), module.name(), module.typeLookup() );
}
ModuleDescriptor modelModule = model.module();
ModuleSpi moduleSpi = (ModuleSpi) modelModule.instance();
EntityStore entityStore = moduleSpi.entityStore();
FunctionStateResolver stateResolver = new FunctionStateResolver(
propertyFunction, associationFunction, manyAssociationFunction, namedAssociationFunction
);
if( identity == null )
{
// Use reference from StateResolver if available
PropertyModel identityModel = (PropertyModel) model
.state()
.findPropertyModelByQualifiedName( IDENTITY_STATE_NAME );
String propertyState = (String) stateResolver.getPropertyState( identityModel );
if( propertyState == null )
{
// Generate reference
IdentityGenerator idGen = moduleSpi.identityGenerator();
identity = idGen.generate( model.types().findFirst().orElse( null ) );
}
else
{
identity = StringIdentity.identity( propertyState );
}
}
return new EntityBuilderInstance<>( model,
this,
uow.getEntityStoreUnitOfWork( entityStore ),
identity,
stateResolver );
}
@Override
public <T> T get( Class<T> type, Identity identity )
throws NoSuchEntityTypeException, NoSuchEntityException
{
Iterable<EntityDescriptor> models = module.typeLookup().lookupEntityModels( type );
if( !models.iterator().hasNext() )
{
throw new NoSuchEntityTypeException( type.getName(), module.name(), module.typeLookup() );
}
return uow.get( EntityReference.create( identity ), this, models, type );
}
@Override
@SuppressWarnings( "unchecked" )
public <T> T get( T entity )
throws NoSuchEntityTypeException
{
EntityComposite entityComposite = (EntityComposite) entity;
EntityInstance compositeInstance = (EntityInstance) compositeInstanceOf( entityComposite );
EntityDescriptor model = compositeInstance.entityModel();
Class<T> type = (Class<T>) compositeInstance.types().findFirst().orElse( null );
return uow.get( compositeInstance.reference(), this, Collections.singletonList( model ), type );
}
@Override
public void remove( Object entity )
throws LifecycleException
{
uow.checkOpen();
EntityComposite entityComposite = (EntityComposite) entity;
EntityInstance compositeInstance = (EntityInstance) compositeInstanceOf( entityComposite );
if( compositeInstance.status() == EntityStatus.NEW )
{
compositeInstance.remove( this );
uow.remove( compositeInstance.reference() );
}
else if( compositeInstance.status() == EntityStatus.LOADED || compositeInstance.status() == EntityStatus.UPDATED )
{
compositeInstance.remove( this );
}
else
{
throw new NoSuchEntityException( compositeInstance.reference(), compositeInstance.types(), usecase() );
}
}
@SuppressWarnings( "DuplicateThrows" )
@Override
public void complete()
throws UnitOfWorkCompletionException, ConcurrentEntityModificationException
{
uow.complete();
}
@Override
public void discard()
{
uow.discard();
}
@Override
public void close()
{
discard();
}
@Override
public boolean isOpen()
{
return uow.isOpen();
}
@Override
public boolean isPaused()
{
return uow.isPaused();
}
@Override
public void pause()
{
uow.pause();
}
@Override
public void resume()
{
uow.resume();
}
@Override
public void addUnitOfWorkCallback( UnitOfWorkCallback callback )
{
uow.addUnitOfWorkCallback( callback );
}
@Override
public void removeUnitOfWorkCallback( UnitOfWorkCallback callback )
{
uow.removeUnitOfWorkCallback( callback );
}
@Override
public boolean equals( Object o )
{
if( this == o )
{
return true;
}
if( o == null || getClass() != o.getClass() )
{
return false;
}
ModuleUnitOfWork that = (ModuleUnitOfWork) o;
return uow.equals( that.uow );
}
@Override
public int hashCode()
{
return uow.hashCode();
}
@Override
public String toString()
{
return uow.toString();
}
void addEntity( EntityInstance instance )
{
uow.addEntity( instance );
}
@Override
public <T extends HasIdentity> T toValue( Class<T> primaryType, T entityComposite )
{
Objects.requireNonNull( primaryType );
if( entityComposite == null )
{
return null;
}
Function<PropertyDescriptor, Object> propertyFunction = new ToValuePropertyMappingFunction( entityComposite );
Function<AssociationDescriptor, EntityReference> assocationFunction = new ToValueAssociationMappingFunction<>( entityComposite );
Function<AssociationDescriptor, Stream<EntityReference>> manyAssocFunction = new ToValueManyAssociationMappingFunction<>( entityComposite );
Function<AssociationDescriptor, Stream<Map.Entry<String, EntityReference>>> namedAssocFunction = new ToValueNameAssociationMappingFunction<>( entityComposite );
ToValueConverter converter = getConverter( ToValueConverter.class );
if( converter != null )
{
propertyFunction = converter.properties( entityComposite, propertyFunction );
assocationFunction = converter.associations( entityComposite, assocationFunction );
manyAssocFunction = converter.manyAssociations( entityComposite, manyAssocFunction );
namedAssocFunction = converter.namedAssociations( entityComposite, namedAssocFunction );
}
@SuppressWarnings( "unchecked" )
ValueBuilder<T> builder = module().instance().newValueBuilderWithState(
primaryType, propertyFunction, assocationFunction, manyAssocFunction, namedAssocFunction );
return builder.newInstance();
}
@Override
public <T extends HasIdentity> Map<String, T> toValueMap( NamedAssociation<T> association )
{
@SuppressWarnings( "unchecked" )
Class<T> primaryType = (Class<T>) api.associationDescriptorFor( association ).type();
return association
.toMap()
.entrySet()
.stream()
.collect( Collectors.toMap( Map.Entry::getKey, entry -> toValue( primaryType, entry.getValue() ) ) );
}
@Override
public <T extends HasIdentity> List<T> toValueList( ManyAssociation<T> association )
{
@SuppressWarnings( "unchecked" )
Class<T> primaryType = (Class<T>) api.associationDescriptorFor( association ).type();
return association
.toList()
.stream()
.map( entity -> toValue( primaryType, entity ) )
.collect( Collectors.toList() );
}
@Override
public <T extends HasIdentity> Set<T> toValueSet( ManyAssociation<T> association )
{
@SuppressWarnings( "unchecked" )
Class<T> primaryType = (Class<T>) api.associationDescriptorFor( association ).type();
return association
.toSet()
.stream()
.map( entity -> toValue( primaryType, entity ) )
.collect( Collectors.toSet() );
}
@Override
public <T extends HasIdentity> T toEntity( Class<T> primaryType, T valueComposite )
{
Objects.requireNonNull( primaryType );
if( valueComposite == null )
{
return null;
}
Function<PropertyDescriptor, Object> propertyFunction = new ToEntityPropertyMappingFunction<>( valueComposite );
Function<AssociationDescriptor, EntityReference> assocationFunction = new ToEntityAssociationMappingFunction<>( valueComposite );
Function<AssociationDescriptor, Stream<EntityReference>> manyAssocFunction = new ToEntityManyAssociationMappingFunction<>( valueComposite );
Function<AssociationDescriptor, Stream<Map.Entry<String, EntityReference>>> namedAssocFunction = new ToEntityNameAssociationMappingFunction<>( valueComposite );
ToEntityConverter converter = getConverter( ToEntityConverter.class );
if( converter != null )
{
propertyFunction = converter.properties( valueComposite, propertyFunction );
assocationFunction = converter.associations( valueComposite, assocationFunction );
manyAssocFunction = converter.manyAssociations( valueComposite, manyAssocFunction );
namedAssocFunction = converter.namedAssociations( valueComposite, namedAssocFunction );
}
try
{
T entity = get( primaryType, valueComposite.identity().get() );
// If successful, then this entity is to by modified.
EntityInstance instance = (EntityInstance) compositeInstanceOf( (EntityComposite) entity );
EntityState state = instance.entityState();
FunctionStateResolver stateResolver = new FunctionStateResolver( propertyFunction,
assocationFunction,
manyAssocFunction,
namedAssocFunction );
EntityModel model = (EntityModel) compositeInstanceOf( (Composite) entity ).descriptor();
stateResolver.populateState( model, state );
return entity;
}
catch( NoSuchEntityException e )
{
EntityBuilder<T> entityBuilder = newEntityBuilderWithState( primaryType,
valueComposite.identity().get(),
propertyFunction,
assocationFunction,
manyAssocFunction,
namedAssocFunction );
return entityBuilder.newInstance();
}
}
private <T> T getConverter( Class<T> converterType )
{
T converter = null;
Usecase usecase = usecase();
if( usecase != null )
{
converter = usecase.metaInfo( converterType );
}
if( converter == null )
{
converter = metaInfo( converterType );
}
return converter;
}
private static EntityState getEntityState( Object entity )
{
return ( (EntityInstance) compositeInstanceOf( (Composite) entity ) ).entityState();
}
private static class UoWQuerySource implements QuerySource
{
private final ModuleUnitOfWork moduleUnitOfWork;
private UoWQuerySource( ModuleUnitOfWork moduleUnitOfWork )
{
this.moduleUnitOfWork = moduleUnitOfWork;
}
@Override
public <T> T find( Class<T> resultType,
Predicate<Composite> whereClause,
List<OrderBy> orderBySegments,
Integer firstResult,
Integer maxResults,
Map<String, Object> variables
)
{
final EntityFinder entityFinder = moduleUnitOfWork.module()
.instance()
.findService( EntityFinder.class )
.get();
try
{
EntityReference foundEntity = entityFinder.findEntity( resultType, whereClause, variables == null ? Collections.emptyMap() : variables );
if( foundEntity != null )
{
try
{
return moduleUnitOfWork.get( resultType, foundEntity.identity() );
}
catch( NoSuchEntityException e )
{
return null; // Index is out of sync - entity has been removed
}
}
// No entity was found
return null;
}
catch( EntityFinderException e )
{
throw new QueryExecutionException( "Finder caused exception", e );
}
}
@Override
public <T> long count( Class<T> resultType,
Predicate<Composite> whereClause,
List<OrderBy> orderBySegments,
Integer firstResult,
Integer maxResults,
Map<String, Object> variables
)
{
EntityFinder entityFinder = moduleUnitOfWork.module().instance().findService( EntityFinder.class ).get();
try
{
return entityFinder.countEntities( resultType, whereClause, variables == null ? Collections.emptyMap() : variables );
}
catch( EntityFinderException e )
{
e.printStackTrace();
return 0;
}
}
@Override
public <T> Stream<T> stream( Class<T> resultType,
Predicate<Composite> whereClause,
List<OrderBy> orderBySegments,
Integer firstResult,
Integer maxResults,
Map<String, Object> variables )
{
EntityFinder entityFinder = moduleUnitOfWork.module().instance().findService( EntityFinder.class ).get();
try
{
return entityFinder.findEntities(
resultType,
whereClause,
orderBySegments,
firstResult,
maxResults,
variables == null ? Collections.emptyMap() : variables
).map( ref ->
{
try
{
return moduleUnitOfWork.get( resultType, ref.identity() );
}
catch( NoSuchEntityException e )
{
// Index is out of sync - entity has been removed
return null;
}
} );
}
catch( EntityFinderException e )
{
throw new QueryExecutionException( "Query '" + toString() + "' could not be executed", e );
}
}
}
private class ToValuePropertyMappingFunction
implements Function<PropertyDescriptor, Object>
{
private Object entity;
ToValuePropertyMappingFunction( Object entity )
{
this.entity = entity;
}
@Override
public Object apply( PropertyDescriptor propertyDescriptor )
{
return getEntityState( entity ).propertyValueOf( propertyDescriptor.qualifiedName() );
}
}
private class ToValueAssociationMappingFunction<T>
implements Function<AssociationDescriptor, EntityReference>
{
private final T entity;
ToValueAssociationMappingFunction( T entity )
{
this.entity = entity;
}
@Override
public EntityReference apply( AssociationDescriptor associationDescriptor )
{
return getEntityState( entity ).associationValueOf( associationDescriptor.qualifiedName() );
}
}
private class ToValueManyAssociationMappingFunction<T>
implements Function<AssociationDescriptor, Stream<EntityReference>>
{
private final T entity;
ToValueManyAssociationMappingFunction( T entity )
{
this.entity = entity;
}
@Override
public Stream<EntityReference> apply( AssociationDescriptor associationDescriptor )
{
return getEntityState( entity ).manyAssociationValueOf( associationDescriptor.qualifiedName() ).stream();
}
}
private class ToValueNameAssociationMappingFunction<T>
implements Function<AssociationDescriptor, Stream<Map.Entry<String, EntityReference>>>
{
private final T entity;
ToValueNameAssociationMappingFunction( T entity )
{
this.entity = entity;
}
@Override
public Stream<Map.Entry<String, EntityReference>> apply( AssociationDescriptor associationDescriptor )
{
return getEntityState( entity ).namedAssociationValueOf( associationDescriptor.qualifiedName() ).stream();
}
}
private class ToEntityPropertyMappingFunction<T>
implements Function<PropertyDescriptor, Object>
{
private final T value;
private ToEntityPropertyMappingFunction( T value )
{
this.value = value;
}
@Override
public Object apply( PropertyDescriptor propertyDescriptor )
{
StateHolder state = getValueStateInstance( value );
Property<Object> property = state.propertyFor( propertyDescriptor.accessor() );
return property.get();
}
}
private class ToEntityAssociationMappingFunction<T>
implements Function<AssociationDescriptor, EntityReference>
{
private final T value;
private ToEntityAssociationMappingFunction( T value )
{
this.value = value;
}
@Override
@SuppressWarnings( "unchecked" )
public EntityReference apply( AssociationDescriptor associationDescriptor )
{
AssociationStateHolder state = getValueStateInstance( value );
AssociationInstance<T> association = (AssociationInstance<T>) state.associationFor( associationDescriptor.accessor() );
return association.getAssociationState().get();
}
}
private class ToEntityManyAssociationMappingFunction<T>
implements Function<AssociationDescriptor, Stream<EntityReference>>
{
private final T value;
private ToEntityManyAssociationMappingFunction( T valueComposite )
{
this.value = valueComposite;
}
@Override
public Stream<EntityReference> apply( AssociationDescriptor associationDescriptor )
{
return getValueStateInstance( value ).manyAssociationFor( associationDescriptor.accessor() ).references();
}
}
private class ToEntityNameAssociationMappingFunction<T>
implements Function<AssociationDescriptor, Stream<Map.Entry<String, EntityReference>>>
{
private final T value;
private ToEntityNameAssociationMappingFunction( T valueComposite )
{
this.value = valueComposite;
}
@Override
public Stream<Map.Entry<String, EntityReference>> apply( AssociationDescriptor associationDescriptor )
{
return getValueStateInstance( value ).namedAssociationFor( associationDescriptor.accessor() ).references();
}
}
private static ValueStateInstance getValueStateInstance( Object value )
{
return (ValueStateInstance) compositeInstanceOf( (Composite) value ).state();
}
}