PDA

View Full Version : Exporting transactional proxy as remoting service


lahma
11-18-2006, 05:44 PM
Hi,

Again I seem to have problems with exporting proxied objects :) I have configuration which is like this (some details removed):

<object id="baseService" type="Spring.Transaction.Interceptor.TransactionProxyFac toryObject, Spring.Data" abstract="true">
<property name="PlatformTransactionManager" ref="transactionManager" />
<property name="TransactionAttributeSource" ref="attributeTransactionAttributeSource"/>
</object>

and a proxy like this:

<object id="userManagement" parent="baseService">
<property name="Target">
<ref object="userManagementImpl"/>
</property>
</object>

and remoting configuration:

<object id="remotedUserManagement" type="Spring.Remoting.SaoExporter, Spring.Services">
<property name="Infinite" value="true" />
<property name="TargetName" value="userManagement"/>
<property name="ServiceName" value="UserManagement.rem"/>
</object>

And implementation class has method with Transaction attribute decorated:

[Transaction()]
public void DoIt()
{
...
}

Now when I try to export my proxy I always get:

Spring.Objects.Factory.ObjectCreationException: Error creating object with name 'remotedUserManagement' defined in 'assembly [My.Assembly, Version=1.0.0.1, Culture=neutral, PublicKeyToken=null], resource [spring-remoting.xml]' : Initialization of object failed : 'RollbackFor' property specified was not found.
---> System.Reflection.CustomAttributeFormatException: 'RollbackFor' property specified was not found. ---> System.NullReferenceException: Object reference not set to an instance of an object. at System.Reflection.CustomAttribute.GetCustomAttribu tes(Module decoratedModule, Int32 decoratedMetadataToken, Int32 pcaCount, RuntimeType attributeFilterType, Boolean mustBeInheritable, IList derivedAttributes)
--- End of inner exception stack trace ---
at System.Reflection.CustomAttribute.GetCustomAttribu tes(Module decoratedModule, Int32 decoratedMetadataToken, Int32 pcaCount, RuntimeTypeattributeFilterType, Boolean mustBeInheritable, IList derivedAttributes)
at System.Reflection.CustomAttribute.GetCustomAttribu tes(RuntimeMethodInfo method, RuntimeType caType, Boolean inherit)
at System.Reflection.RuntimeMethodInfo.GetCustomAttri butes(Boolean inherit)
at Spring.Proxy.AbstractProxyTypeBuilder.GetMethodAtt ributes(MethodInfo method)
at Spring.Proxy.AbstractProxyTypeBuilder.ApplyCustomA ttributes(MethodBuilderbuilder, MethodInfo targetMethod)
at Spring.Proxy.AbstractProxyTypeBuilder.ImplementInt erface(TypeBuilder typeBuilder, IProxyMethodBuilder proxyMethodBuilder, Type intf, Type targetType, Boolean proxyVirtualMethods)
at Spring.Proxy.AbstractProxyTypeBuilder.ImplementInt erface(TypeBuilder typeBuilder, IProxyMethodBuilder proxyMethodBuilder, Type intf, Type targetType)
at Spring.Proxy.CompositionProxyTypeBuilder.BuildProx yType()
at Spring.Remoting.Support.RemoteObjectProxyTypeBuild er.BuildProxyType()
at Spring.Remoting.SaoExporter.Export()
at Spring.Remoting.SaoExporter.AfterPropertiesSet()
at Spring.Objects.Factory.Support.AbstractAutowireCap ableObjectFactory.Invoke
InitMethods(Object target, String name, IConfigurableObjectDefinition definition
)

This is the same with normal ProxyFactoryObject too. What am I doing wrong? :(

Mark Pollack
11-28-2006, 07:02 PM
Hi,

I never tried this yet, it should be doable. I'll be looking into it.. What version are you using? In the meantime you can use composition to delegate to a tx-proxied object....

Mark

lahma
12-01-2006, 10:10 AM
Hi again,

The problem occurs even with the latest nightly build. Maybe I need to go for delegation. Delegation is extra clutter but if it's the only way for now.

Marko

lahma
12-05-2006, 02:30 PM
I found a way to get by this. Now I use normal ProxyFactoryObject which has the transaction interceptor attached to it. I also needed to set my Transaction attribute as follows to prevent the exception while initializing context:


[Transaction(RollbackFor=new Type[] { typeof(Exception) })]


But for some reason I still have trouble using the functionality. I'm using TxScopeTransactionManager, TransactionInterceptor and AttributesTransactionAttributeSource. Transaction is created as expected but then...

* When service encounters an exception (I created a NullReferenceException by trying to access fields for null object) the connection closes so that any calls using the same DbProvider etc complain about connection being closed.

* I looked a bit the TransactionInterceptor.cs:


try
{
// This is an around advice.
// Invoke the next interceptor in the chain.
// This will normally result in a target object being invoked.
returnValue = invocation.Proceed();
}
catch ( Exception ex )
{
// target invocation exception
DoCloseTransactionAfterThrowing( txnInfo, ex );
throw ex;
}
finally
{
DoFinally( txnInfo );
}
DoCommitTransactionAfterReturning( txnInfo );
return returnValue;


* Why is DoCommitTransactionAfterReturning always called even if there was an error? Looks weird to me.

* Why is there a "throw ex;" when there should be just plain throw; I believe?

Mark Pollack
12-05-2006, 09:39 PM
Hi,

Sorry about this...I am busy packaging up the release but I want to resolve this. I am doing my own testing now. The adding of the generic Exception in the rollback seems unecessary...what exception is throw otherwise? I agree about the throw vs throw ex. Anyway, I'll get back to you later today.

Cheers,
Mark

lahma
12-06-2006, 09:42 AM
Hi Mark,

The code I used to generate my weird situation is roughly as follows:


[Transaction(RollbackFor=new Type[] { typeof(Exception) })]
public void TestTransaction(string guidString)
{
// tx is not-null, all fine here with interceptor
Transaction tx = Transaction.Current;
User u = GetUser("jdoe);

Session s = new Session();
s.SessionId = guidString;
s.User = u;
s.Created = DateTime.Now;

// this goes fine
userDao.AddSession(s);

// should crash..
userDao.UpdateUser(null);


The reason for the RollbackFor was earlier mentioned export of proxy to remoting. It complained about missing the attribute (only when remoting exporters were defined in configuration).

I'm using NHibernate template to add a session to database with my generated id (GUID) and then I generate an exception in service method. This caught in my unit test where I then try to load the session from database with the id (using the same service which delegates again to DAO with NHibernateTemplate). Then I get the closed state from connection.

If I'm using System.Transactions transaction management with Spring shouldn't it apply automatically to NHibernate too because its nature is global?

Here's a part of my Spring configuration, merged from different files:




<object id="baseService" type="Spring.Aop.Framework.ProxyFactoryObject, Spring.Aop" abstract="true">
<property name="InterceptorNames">
<list>
<value>transactionInterceptor</value>
</list>
</property>
</object>

<object id="userManagement" parent="baseService">
<property name="Target">
<object type="MyProject.BusinessLogic.UserManagement, MyProject.BusinessLogic">
<property name="UserDao" ref="userDao" />
</object>
</property>
</object>

<!-- Transaction Interceptor -->
<object id="transactionInterceptor"
type="Spring.Transaction.Interceptor.TransactionIntercep tor, Spring.Data">
<property name="TransactionManager" ref="transactionManager"/>
<property name="TransactionAttributeSource" ref="attributeTransactionAttributeSource"/>
</object>

<object id="attributeTransactionAttributeSource"
type="Spring.Transaction.Interceptor.AttributesTransacti onAttributeSource, Spring.Data">
</object>


<!-- Ado Template -->
<object id="adoTemplate" type="Spring.Data.AdoTemplate, Spring.Data" >
<property name="DbProvider" ref="dbProvider"/>
</object>

<object id="transactionManager"
type="Spring.Data.TxScopeTransactionManager, Spring.Data">
</object>

<!-- Hibernate Configuration -->

<object id="dbProvider" type="Spring.Data.Common.DbProviderFactory, Spring.Data" factory-method="GetDbProvider">
<constructor-arg index="0" value="System.Data.SqlClient" />
<property name="ConnectionString" value="${db.connection.string}"/>
</object>

<object id="sessionFactory" type="MyProject.DataAccess.Hibernate.LocalSessionFactory Object, MyProject.DataAccess">
<property name="DbProvider" ref="dbProvider"/>
<property name="HibernateProperties">
<dictionary>
<entry key="hibernate.connection.provider" value="NHibernate.Connection.DriverConnectionProvider"/>
<entry key="hibernate.dialect" value="NHibernate.Dialect.MsSql2005Dialect"/>
<entry key="hibernate.connection.driver_class" value="NHibernate.Driver.SqlClientDriver"/>
</dictionary>
</property>
</object>

<object id="hibernateTemplate" type="MyProject.DataAccess.Hibernate.HibernateTemplate, MyProject.DataAccess">
<property name="SessionFactory" ref="sessionFactory"/>
</object>

<object id="hibernateTransactionManager" type="Spring.Data.NHibernate.HibernateTransactionManager , Spring.Data.NHibernate">
<property name="DbProvider" ref="dbProvider"/>
<property name="sessionFactory" ref="sessionFactory"/>
</object>


Template and SessionFactory are just inherting the Spring ones, template adds a few shorthands (not used in test) and factory loads configuration from NHibernate.Mapping.Attributes.

Thanks for your great work and help!

-Marko

Mark Pollack
12-06-2006, 08:20 PM
Hi,

Thanks for the detailed explination. I understand what you are trying to do. I don't quite know why subsequent uses of HibernateTemplate after the rollback are not getting a new connection from the DbProvider. I will reproduce the issue and get back to you. I also had another question today regarding a similar issue using TxScope - my initial testing showed the correct behavior but that was a while ago. Unit tests are a big priority to making a 1.0 preview release for the hibernate module (I'll be creating another JIRA category next week for this so we can trace the release and issues like the ones you are raise more accurately.).

I did make a bug fix regarding rollback behavior - so get the latest from CVS, there have been alot of issues with correct nant behavior when testing on multiple platforms so the nightly build is a bit out of whack. The release will out today to you can get it that way as well.

As for the question in the interceptor...
Why is DoCommitTransactionAfterReturning always called even if there was an error? Looks weird to me.


The rethrow will prevent that line from ever being called in the case of an exception thrown in the target method.

Sorry can't give a full answer just yet,hang in there.

Cheers,
Mark

lahma
12-06-2006, 09:02 PM
As for the question in the interceptor...

The rethrow will prevent that line from ever being called in the case of an exception thrown in the target method.


Ok, but still, when you think of it the finally block is run before the DoCommitTransactionAfterReturning, so shouldn't it be something like DoCommitTransactionAfterReturningAndAfterFinally - just a bit confusing for me.

Thanks for your prompt reply, I'll give it a whirl with the latest version tomorrow, it's soon bed time here in Scandinavia. The remoting proxy problem still bugs me though :)

-Marko

Bruno Baia
12-15-2006, 01:27 AM
Hi,

I resolved the bug to allow classes with Transaction attribute to be proxied.
I've got the same problem while trying to apply aspects on transactional services.

RollbackFor and NoRollbackFor fields wasn't initialized, that's why we got the errors.
You can get the latest code from CVS or made the changes by yourself.

In springnet/Spring.Net/src/Spring/Spring.Data/Transaction/Interceptor/TransactionAttribute.cs :

Replace

private bool _readOnly;
private Type[] _rollbackTypes;
private Type[] _noRollbackTypes;

By

private bool _readOnly = false; // optional
private Type[] _rollbackTypes = Type.EmptyTypes;
private Type[] _noRollbackTypes = Type.EmptyTypes;



Bruno

lahma
12-15-2006, 02:38 PM
Thanks Bruno, that was magic I needed! No worries with attribute anymore.

The problem with transaction screwing NHibernate up still seems to still be there though. Well, I need to investicate more.

Thanks again,

-Marko