blob: c68438502002097cc161138cde77be3b344cc7a3 [file] [log] [blame]
/* Copyright 2008 Edward Yakop.
* Copyright 2009 Niclas Hedhman.
*
* 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.qi4j.api.unitofwork.concern;
import java.lang.reflect.Method;
import org.qi4j.api.common.AppliesTo;
import org.qi4j.api.concern.GenericConcern;
import org.qi4j.api.injection.scope.Invocation;
import org.qi4j.api.injection.scope.Structure;
import org.qi4j.api.injection.scope.Uses;
import org.qi4j.api.structure.Module;
import org.qi4j.api.unitofwork.ConcurrentEntityModificationException;
import org.qi4j.api.unitofwork.UnitOfWork;
import org.qi4j.api.usecase.Usecase;
import org.qi4j.api.usecase.UsecaseBuilder;
/**
* {@code UnitOfWorkConcern} manages the unit of work complete, discard and retry policy.
*
* @see UnitOfWorkPropagation
* @see UnitOfWorkDiscardOn
*/
@AppliesTo( UnitOfWorkPropagation.class )
public class UnitOfWorkConcern
extends GenericConcern
{
private static final Class<?>[] DEFAULT_DISCARD_CLASSES = new Class[]{ Throwable.class };
@Structure
Module module;
@Invocation
UnitOfWorkPropagation propagation;
/**
* Handles method with {@code UnitOfWorkPropagation} annotation.
*
* @param proxy The object.
* @param method The invoked method.
* @param args The method arguments.
*
* @return The returned value of method invocation.
*
* @throws Throwable Thrown if the method invocation throw exception.
*/
@Override
public Object invoke( Object proxy, Method method, Object[] args )
throws Throwable
{
UnitOfWorkPropagation.Propagation propagationPolicy = propagation.value();
if( propagationPolicy == UnitOfWorkPropagation.Propagation.REQUIRED )
{
if( module.isUnitOfWorkActive() )
{
return next.invoke( proxy, method, args );
}
else
{
Usecase usecase = usecase();
return invokeWithCommit( proxy, method, args, module.newUnitOfWork( usecase ) );
}
}
else if( propagationPolicy == UnitOfWorkPropagation.Propagation.MANDATORY )
{
if( !module.isUnitOfWorkActive() )
{
throw new IllegalStateException( "UnitOfWork was required but there is no available unit of work." );
}
}
else if( propagationPolicy == UnitOfWorkPropagation.Propagation.REQUIRES_NEW )
{
Usecase usecase = usecase();
return invokeWithCommit( proxy, method, args, module.newUnitOfWork( usecase ) );
}
return next.invoke( proxy, method, args );
}
private Usecase usecase()
{
String usecaseName = propagation.usecase();
Usecase usecase;
if( usecaseName == null )
{
usecase = Usecase.DEFAULT;
}
else
{
usecase = UsecaseBuilder.newUsecase( usecaseName );
}
return usecase;
}
protected Object invokeWithCommit( Object proxy, Method method, Object[] args, UnitOfWork currentUnitOfWork )
throws Throwable
{
try
{
UnitOfWorkRetry retryAnnot = method.getAnnotation( UnitOfWorkRetry.class );
int maxTries = 0;
long delayFactor = 0;
long initialDelay = 0;
if( retryAnnot != null )
{
maxTries = retryAnnot.retries();
initialDelay = retryAnnot.initialDelay();
delayFactor = retryAnnot.delayFactory();
}
int retry = 0;
while( true )
{
Object result = next.invoke( proxy, method, args );
try
{
currentUnitOfWork.complete();
return result;
}
catch( ConcurrentEntityModificationException e )
{
if( retry >= maxTries )
{
throw e;
}
module.currentUnitOfWork().discard();
Thread.sleep( initialDelay + retry * delayFactor );
retry++;
currentUnitOfWork = module.newUnitOfWork( usecase() );
}
}
}
catch( Throwable throwable )
{
// Discard only if this concern create a unit of work
discardIfRequired( method, currentUnitOfWork, throwable );
throw throwable;
}
}
/**
* Discard unit of work if the discard policy match.
*
* @param aMethod The invoked method. This argument must not be {@code null}.
* @param aUnitOfWork The current unit of work. This argument must not be {@code null}.
* @param aThrowable The exception thrown. This argument must not be {@code null}.
*/
protected void discardIfRequired( Method aMethod, UnitOfWork aUnitOfWork, Throwable aThrowable )
{
UnitOfWorkDiscardOn discardPolicy = aMethod.getAnnotation( UnitOfWorkDiscardOn.class );
Class<?>[] discardClasses;
if( discardPolicy != null )
{
discardClasses = discardPolicy.value();
}
else
{
discardClasses = DEFAULT_DISCARD_CLASSES;
}
Class<? extends Throwable> aThrowableClass = aThrowable.getClass();
for( Class<?> discardClass : discardClasses )
{
if( discardClass.isAssignableFrom( aThrowableClass ) )
{
aUnitOfWork.discard();
}
}
}
}