/*
* Copyright 2008 Edward Yakop.
* Copyright 2009 Niclas Hedhman.
* Copyright 2009 Michael Hunger.
*
* 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.library.unitofwork;
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.unitofwork.UnitOfWork;
import org.qi4j.api.unitofwork.UnitOfWorkCompletionException;
import org.qi4j.api.unitofwork.UnitOfWorkFactory;
/**
* {@code UnitOfWorkConcern} manages the unit of work complete and discard policy.
*
* @see org.qi4j.api.unitofwork.UnitOfWorkPropagation
* @see org.qi4j.api.unitofwork.UnitOfWorkDiscardOn
*/
@AppliesTo( UnitOfWorkPropagation.class )
public class UnitOfWorkConcern
extends GenericConcern
{
@Structure
private UnitOfWorkFactory uowf;
@Invocation
private 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.
*/
public Object invoke( Object proxy, Method method, Object[] args )
throws Throwable
{
UnitOfWorkPropagation.Propagation propagationPolicy = propagation.value();
if( propagationPolicy == UnitOfWorkPropagation.Propagation.REQUIRED )
{
return requiredStrategy( proxy, method, args );
}
else if( propagationPolicy == UnitOfWorkPropagation.Propagation.MANDATORY )
{
return mandatoryStrategy( proxy, method, args );
}
else if( propagationPolicy == UnitOfWorkPropagation.Propagation.REQUIRES_NEW )
{
return requiresNewRequires( proxy, method, args );
}
throw new UnitOfWorkPropagationException( "'null' is not allowed as propagation strategy." );
}
/**
* 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 exceptionThrown The exception thrown. This argument must not be {@code null}.
*
* @throws org.qi4j.api.unitofwork.UnitOfWorkCompletionException
* If the complete() method fails.
*/
private void discardIfRequired( Method aMethod, UnitOfWork aUnitOfWork, Throwable exceptionThrown )
throws UnitOfWorkCompletionException
{
UnitOfWorkDiscardOn discardPolicy = aMethod.getAnnotation( UnitOfWorkDiscardOn.class );
UnitOfWorkNoDiscardOn noDiscardPolicy = aMethod.getAnnotation( UnitOfWorkNoDiscardOn.class );
if( null == discardPolicy && noDiscardPolicy == null )
{
aUnitOfWork.discard();
return;
}
Class<?>[] discardClasses;
if( discardPolicy == null )
{
discardClasses = new Class[]{ Throwable.class };
}
else
{
discardClasses = discardPolicy.value();
}
Class<?>[] noDiscardClasses;
if( noDiscardPolicy == null )
{
noDiscardClasses = new Class[0];
}
else
{
noDiscardClasses = noDiscardPolicy.value();
}
Class<? extends Throwable> thrownClass = exceptionThrown.getClass();
next:
for( Class<?> discardClass : discardClasses )
{
if( discardClass.isAssignableFrom( thrownClass ) )
{
for( Class<?> noDiscardClass : noDiscardClasses )
{
if( noDiscardClass.isAssignableFrom( thrownClass ) )
{
continue next;
}
}
aUnitOfWork.discard();
return;
}
}
aUnitOfWork.complete();
}
private UnitOfWork createNewUnitOfWork()
{
return uowf.newUnitOfWork();
}
private Object requiresNewRequires( Object proxy, Method method, Object[] args )
throws Throwable
{
UnitOfWork currentUnitOfWork = createNewUnitOfWork();
try
{
Object result = next.invoke( proxy, method, args );
currentUnitOfWork.complete();
return result;
}
catch( Throwable throwable )
{
discardIfRequired( method, currentUnitOfWork, throwable );
throw throwable;
}
}
private Object mandatoryStrategy( Object proxy, Method method, Object[] args )
throws Throwable
{
UnitOfWork currentUnitOfWork = uowf.currentUnitOfWork();
if( currentUnitOfWork == null )
{
throw new UnitOfWorkPropagationException( "[UnitOfWork] was required but there is no available unit of work." );
}
return next.invoke( proxy, method, args );
}
private Object requiredStrategy( Object proxy, Method method, Object[] args )
throws Throwable
{
UnitOfWork currentUnitOfWork = uowf.currentUnitOfWork();
boolean created = false;
if( currentUnitOfWork == null )
{
currentUnitOfWork = createNewUnitOfWork();
created = true;
}
try
{
Object result = next.invoke( proxy, method, args );
if( created )
{
currentUnitOfWork.complete();
}
return result;
}
catch( Throwable throwable )
{
// Discard only if this concern create a unit of work
if( created )
{
discardIfRequired( method, currentUnitOfWork, throwable );
}
throw throwable;
}
}
}