View Full Version : Spring Enterprise Services (com+)
steinard
12-01-2006, 03:09 PM
Hi!
I am looking at how Spring can support using com+ services, and reading chapter 20 in Spring's latest documentation I realised that com+ services are supported. Great.... So I went to the calculator example, loaded the solution file in 2005, compiled the solution.
Then I ran the register component services, as the calculator service probably have to be registered in gac (signed, I already saw a .snk file) etc. Then I tried to run the client after having modified the app.config to use enterprice services instead of "in process":
<resource uri="assembly://Client/Client.Config.EnterpriseServices/enterpriseServices.xml" />
When starting the client I get the following exception:
---------------------------------------
Get Calculator...
Spring.Objects.Factory.ObjectCreationException: Error creating object with name '' :
IFactoryObject threw exception on object creation. ---> System.ArgumentNullException: Value cannot be null.
Parameter name: type
at System.Activator.CreateInstance(Type type, Boolean nonPublic)
at System.Activator.CreateInstance(Type type)
at Spring.EnterpriseServices.ServicedComponentFactory .CreateInstance() in C:\dev_srv\src\middleware\spring\src\Spring\Spring .Services\EnterpriseServices\ServicedComponentFact ory.cs:line 173
at Spring.EnterpriseServices.ServicedComponentFactory .GetObject() in C:\dev_srv\src\middleware\spring\src\Spring\Spring .Services\EnterpriseServices\ServicedComponentFact ory.cs:line 118
at Spring.Objects.Factory.Support.AbstractObjectFacto ry.GetObjectForSharedInstance(String name, Object instance) in C:\dev_srv\src\middleware\spring\src\Spring\Spring .Core\Objects\Factory\Support\AbstractObjectFactor y.cs:line 609
--- End of inner exception stack trace ---
at Spring.Objects.Factory.Support.AbstractObjectFacto ry.GetObjectForSharedInstance(String name, Object instance) in C:\dev_srv\src\middleware\spring\src\Spring\Spring .Core\Objects\Factory\Support\AbstractObjectFactor y.cs:line 613
at Spring.Objects.Factory.Support.AbstractObjectFacto ry.GetObject(String name, Type requiredType, Object[] arguments) in C:\dev_srv\src\middleware\spring\src\Spring\Spring .Core\Objects\Factory\Support\AbstractObjectFactor y.cs:line 221
at Spring.Objects.Factory.Support.AbstractObjectFacto ry.GetObject(String name, Object[] arguments) in C:\dev_srv\src\middleware\spring\src\Spring\Spring .Core\Objects\Factory\Support\AbstractObjectFactor y.cs:line 1240
at Spring.Objects.Factory.Support.AbstractObjectFacto ry.GetObject(String name) in C:\dev_srv\src\middleware\spring\src\Spring\Spring .Core\Objects\Factory\Support\AbstractObjectFactor y.cs:line 1200
at Spring.Context.Support.AbstractApplicationContext. GetObject(String name) in C:\dev_srv\src\middleware\spring\src\Spring\Spring .Core\Context\Support\AbstractApplicationContext.c s:line 1122
at
Client.MainClient.Main() in C:\dev_srv\src\middleware\spring\examples\Spring\S pring.Examples.Calculator\Client\MainClient.cs:lin e 48
---------------------------------------
Since I am trying to understand how to specify com+ services with spring.net and how clients should consuming these services, I am having a hard time figuring out what goes wrong in this example. I see from the exception that an argument given to the ServicedComponentFactory.CreateInstance() cannot produce an object without a legal name value, so where should I specify this name?
I thought that the object definition in enterpriseServices.xml already did this:
<object id="calculatorService" type="Spring.EnterpriseServices.ServicedComponentFactory , Spring.Services">
<property name="Name" value="calculatorComponent" />
<property name="Template" value="calculatorServiceTemplate" />
</object>
I guess I am missing something here, but I thought that my app.config file pointed to the enterpriseServices.xml file when I commented in:
<resource uri="assembly://Client/Client.Config.EnterpriseServices/enterpriseServices.xml" />
and commented out the "in-process" reference. I would love to get this example to work, so any help is appriciated.
Thanks,
Steinar.
Bruno Baia
12-01-2006, 03:25 PM
Hi Steinar,
You did everything well, but as you seen, the sample is not working because it needs signed Spring assemblies...
In the latest code, I've added a note to say that Enterprise services example does not work with nightly builds.
Be sure it will work with the 1.1 P3 scheduled for this week-end.
Bruno
steinard
12-01-2006, 05:59 PM
Great! I will be checking sourceforge regulary for release 1.1 P3....
Bruno Baia
12-04-2006, 01:44 AM
I fixed the sample.
It should be avalaible with nightly builds too.
Bruno
steinard
12-05-2006, 03:08 PM
:D
Hey, this works now, great and thanks again.
I have been reading about com+ lately (as I'm from the Java world and new in the .Net domain) and I've found several warnings about the costs of com+ communication. Do you know if spring encourages certain patterns for working around these costly server calls (like having a facade retrieve more data then asked for to reduce the number of datafetches), are there any patterns you would recommend?
Thanks,
Steinar.
Aleks Seovic
12-07-2006, 07:50 AM
As you guessed, you will want to apply the same design principles to your com+ services as you would do to any out-of-process, possibly physically remote service. Basically, the same rules that apply to session EJBs or RMI objects apply to com+ objects as well.
That means preferring "chunky" to "chatty" services in order to minimize the number of network hops to achieve a certain task. Of course, you need to balance it so you don't bring the whole database to the client when all you need is a few objects. Bottom line is that it is more art than science and you should test perfromance of different approaches to see which one works best for your particular usage scenario.
- Aleks
steinard
12-14-2006, 02:26 PM
Hi again!
I have been playing with the calculator example to increase my understanding of how Spring.Net will expose the component services. I wonder if it was possible to transfer my domain classes from server to client by value instead of by reference?
Any help for increasing my understanding here is appreciated,
thanks,
Steinar.
steinard
01-31-2007, 09:14 AM
I ended up using the traditional way of exposing a component service (extending ServicedComponent) for now, maybe I'll refactor it later.
In addition the build- system will generate my DTOs (structs) and and use reflection to populate the DTOs with data before transfering data between client and server (a pretty simple way to map the object graph if needed). All data is thus transfered by value and none by reference.
Cheers,
Steinar.
Erich Eichinger
02-04-2007, 01:46 PM
Hi Steinar,
I never used DCOM with NET so I'm just curious: shouldn't passing your Domain objects be possible by simply marking the [Serializable] and distributing the assembly to both, the server and the client?
-Erich
steinard
02-12-2007, 10:27 AM
Hi Erich!
I suppose that can be made possible if one knows how to write a custom marshaler. The default marshaler does not know how to pass custom classes, and having the assemblies (domain contracts and implementations) located at both ends will not help much then. The default way is to transfer the domain objects by reference (receiving a proxy at the client side), and this may work out great in many scenarios.
One downside with this approach is that you'll get many unwanted network trips that could have been avoided if you have better control over the objects at the client side. Also having a reference back to the server for every object in the object graph for each logged on user would not be good for scalability. So therefore I would like to release all resources and keep the server stateless and use reflection to build and populate DTOs.
I have some URLs somewhere talking about these problems, I'll send them to you when I'm able to locate them.
Another thing, I've seen examples of com+ using queued components to serialize the complete objct graph in one go, but I've been told that I should not trust MSMQ and that I should try to avoid it. I've started looking at ActiveMQ, any chance that Spring is interested in integrating against that? Maybe some of the integration code for Tibco may be reused/altered? ActiveMQ is developed in Java and uses IKVM to be compiled into .Net assembly.
Thanks for your interest in dcom related problems, I get the impression that everyone is doing webapps these days.
Cheers,
Steinar.
Erich Eichinger
02-12-2007, 11:14 AM
Hi Steinar,
I'm using MSMQ for 3 years now and didn't have much problems with it. But I admit I use only very limited functionality of it. Asfaik Mark is doing some implementation for MSMQ - I don't know about any plans about ActiveMQ.
Regarding Custom-Marshalers: Recently I came across a COM-Interop assembly CDO.dll generated by tlbimp.exe and looked at it using ildasm/reflector - it contained pretty nice examples of Custom-Marshalers.
Passing domain objects by reference is definitely a show-stopper. I'm not sure if custom marshalling really solves this issue, but it's worth a try.
cheers,
Erich
steinard
02-12-2007, 12:52 PM
Hi!
I might give it a shot, but right now my main concern is to get the service running with full spring and nhibernate configuration (I've got a demonstration of the new middleware on the 26th, so it better work by the 25th...). In a scaled down sample server (without any DB-communication) I've tested reflection based population of the DTOs on the server and the reversed reconstruction of the object graph (also based on reflecetion) on the client side, and that worked pretty fast for the most common cases, so for now I'll stick to this solution. Since the DTOs are generated during build, it's a layer that I do not have to maintain, so the pain of having DTOs isn't that bad.
Right now I'm stuck on MySql.Data.dll is of another version then the one described in dbproviders.xml. You do not by any chance have MySql.Data of Version=1.0.7.30072? I've spent some time trying to dig up that version on the net to no avail.
Thanks,
Steinar.
Erich Eichinger
02-12-2007, 04:25 PM
Hi,
I might give it a shot
This would be great, if you could clearify this.
Regarding MySql: Using <bindingRedirect> is no option?
-Erich
steinard
02-12-2007, 07:43 PM
Hi!
This would be great, if you could clearify this.
I'll spend some time looking into how hard it is to write a custom marshaler by trying to do this in a really small and simple sample component. But I do not have time at the moment, that was what I meant. I also doubt that custom marshalling will solve the problem as I suspect you have to write one for each type of class to transfer, and I'm not sure how easy it is to use reflection in c++ to make something more generic to take in the complete object graph. I mentioned earlier that I had a few links on the subject of custom marshalling and object graph serialization, here they are:
http://www.codeproject.com/dotnet/qcsolution.asp
http://www.codeproject.com/com/CustomMarshaling01.asp
Anyway, the first thing I tried was bindingredirection, but then someone told me that I could use Fusion Assembly Log Viewer to see if the serviced component is able to load all the assemblies referenced in the application.config file.
<assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">
<dependentAssembly>
<assemblyIdentity name="MySql.Data" publicKeyToken="c5687fc88969c44d" culture="neutral"/>
<bindingRedirect oldVersion="1.0.7.30072" newVersion="5.0.3.0"/>
</dependentAssembly>
</assemblyBinding>
The Fusion assembly binding log viewer tells me that the file MySql.Data.resources.dll cannot be found in GAC or at the defined component's home directory. Google searches for MySql.Data.resources.dll did not lead to anything useful.
Here is a small dump of the fusion log, unfortunately it's in Norwegian but it might give you an idea...
LOG: Etter-policy-referanse: MySql.Data.resources, Version=5.0.3.0, Culture=no, PublicKeyToken=c5687fc88969c44d
LOG: GAC-oppslag mislyktes.
LOG: Forsøk på nedlasting av ny URL file:///C:/dev/src/new_efm/EFM_ComMiddlewareService/bin/Debug/no/MySql.Data.resources.DLL.
LOG: Forsøk på nedlasting av ny URL file:///C:/dev/src/new_efm/EFM_ComMiddlewareService/bin/Debug/no/MySql.Data.resources/MySql.Data.resources.DLL.
LOG: Forsøk på nedlasting av ny URL file:///C:/dev/src/new_efm/EFM_ComMiddlewareService/bin/Debug/no/MySql.Data.resources.EXE.
LOG: Forsøk på nedlasting av ny URL file:///C:/dev/src/new_efm/EFM_ComMiddlewareService/bin/Debug/no/MySql.Data.resources/MySql.Data.resources.EXE.
LOG: Alle undersøkende URL-adresser forsøkt og mislyktes.
I have never even heard of MySql.Data.resources.DLL, anyone ever experienced this before?
Thanks,
Steinar.
Erich Eichinger
02-12-2007, 08:38 PM
Hi,
asfaik for many assemblies loaded into an AppDomain there will be an additional lookup for satellite-assemblies. But usually this doesn't cause an error.
If you can't find a better way you could register for the AppDomain.UnhandledException event in your serviced component to capture and log (maybe using Debug.Trace) binding failures. In addition to that you can register for AppDomain.AssemblyResolve to capture and log resolve requests. There are 2 tools I use for tracking loading issues: DebugView, which captures Debug.Trace output and FileMonitor, which traces fileaccess (both from www.sysinternals.com).
First of all I'd try to execute the code from within the controlled environment of a unit test or from a console application before invoking as a serviced component to see what really happens.
hope this helps,
Erich
steinard
02-12-2007, 08:54 PM
Hi!
I'll check out the tools, coming from Java I'm quite blank on debugging tools in .Net! Thanks for the input.
I created a dummy project (console application) that simply reads in the configuration file to test that the configuration file was correctly written. Everything works when launching the test program reusing the same assemblies, dumping the db, rebuilding it based on the attributes defined in the domain model assemblies and populating the db from insert scripts in one go. I dunno why this behaves differently when moving to a com+ serviced component.
By the way, I just searched for .resources.dll and hit this page (http://msdn.microsoft.com/library/default.asp?url=/library/en-us/cptutorials/html/resources_and_localization_using_the__net_framewor k_sdk.asp) and this page (http://edndoc.esri.com/arcobjects/8.3/GettingStarted/WorkingwithResources.htm).
Might look as if this has something to do with localization.
Thanks,
Steinar.
steinard
02-12-2007, 10:36 PM
Pulling down the MySql connector source and recompiling it removed the error, but I still get an exception loading the spring configuration on the server side and the component returns HRESULT: 0x80131902.
The Fusion log is now empty, I guess I must use the tools mentioned above to find out what goes wrong. Also, if someone knows about some other tools to log com+ server side activities, then I'm very interested.
Thanks for all advices so far,
Steinar.
steinard
02-13-2007, 09:24 AM
Hi!
Thanks for the tip Erich. Registering for the AppDomain.UnhandledException event in the serviced component finally gave me an useful exception. Now I wonder if I should recompile spring myself to satisfy the version of MySql specified in Spring.Data.Common.dbproviders.xml as bindingredirection obviously don't solve the problem, or if anyone can provide me with version 1.0.7.30072 of MySql.Data.dll
Here is the nice exception:
Spring.Objects.Factory.UnsatisfiedDependencyExcept ion:
Error thrown by a dependency of object 'MySql' defined in 'assembly
[Spring.Data, Version=1.1.0.2, Culture=neutral, PublicKeyToken=595f7ff98f609902],
resource [Spring.Data.Common.dbproviders.xml]' :
Unsatisfied dependency expressed through constructor argument with
index 2 of type [System.Type] : Could not convert constructor argument value
[MySql.Data.MySqlClient.MySqlConnection, MySql.Data, Version=1.0.7.30072, Culture=neutral, PublicKeyToken=c5687fc88969c44d] to required type [System.Type] :
Cannot convert property value of type [System.String] to required type [System.Type] for property ''.
while resolving 'constructor argument with name dbmetadata' to '(inner object)'
defined in 'assembly [Spring.Data, Version=1.1.0.2, Culture=neutral, PublicKeyToken=595f7ff98f609902], resource [Spring.Data.Common.dbproviders.xml]'
at Spring.Objects.Factory.Support.AbstractAutowireCap ableObjectFactory.ResolveInnerObjectDefinition(Str ing name, String innerObjectName, String argumentName, IObjectDefinition definition, Boolean singletonOwner)
at Spring.Objects.Factory.Support.AbstractAutowireCap ableObjectFactory.ResolveValueIfNecessary(String name, RootObjectDefinition definition, String argumentName, Object argumentValue)
at Spring.Objects.Factory.Support.AbstractAutowireCap ableObjectFactory.ResolveConstructorArguments(Stri ng name, RootObjectDefinition definition, ConstructorArgumentValues resolvedValues)
at Spring.Objects.Factory.Support.AbstractAutowireCap ableObjectFactory.AutowireConstructor(String name, RootObjectDefinition definition, ConstructorArgumentValues argumentValues)
at Spring.Objects.Factory.Support.AbstractAutowireCap ableObjectFactory.CreateObject(String name, RootObjectDefinition definition, Object[] arguments, Boolean allowEagerCaching)
at Spring.Objects.Factory.Support.AbstractAutowireCap ableObjectFactory.CreateObject(String name, RootObjectDefinition definition, Object[] arguments)
at
Spring.Objects.Factory.Support.AbstractObjectFacto ry.GetObject(String name, Type requiredType, Object[] arguments)
at
Spring.Objects.Factory.Support.AbstractObjectFacto ry.GetObject(String name, Type requiredType)
at Spring.Context.Support.AbstractApplicationContext. GetObject(String name, Type requiredType)
at Spring.Data.Common.DbProviderFactory.GetDbProvider (String providerInvariantName)
at Spring.Data.Common.DbProviderFactoryObject.GetObje ct()
at Spring.Objects.Factory.Support.AbstractObjectFacto ry.GetObjectForSharedInstance(String name, Object instance)
Thanks,
Steinar.
Erich Eichinger
02-13-2007, 11:22 AM
Hi,
Obviously the Type "MySql.Data, Version=1.0.7.30072, Culture=neutral, PublicKeyToken=c5687fc88969c44d" can't be loaded by the runtime. During component startup check in FileMonitor where the runtime is looking for MySql.Data.dll. Are you sure, you don't see anything in the Fusion Log Viewer? I'm still convinced, that version redirection should work. Try to register for AppDomain.AssemblyResolve event to check, if the runtime is at least *trying* to load the assembly.
Another thing you could try is to copy the <object id="MySql"> definition from dbproviders.xml and create a new one <object id="MyMySql"> using your library's type version and use "MyMySql" as <db:dbProvider>.
-Erich
steinard
02-13-2007, 01:52 PM
Hi!
Since I don't have version '1.0.7.30072', that version cannot be loaded but I do have version '5.0.3.0' and I redirect to this version in the application.config file in the components defined home directory specified on the activation tab.
<assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">
<dependentAssembly>
<assemblyIdentity name="MySql.Data" publicKeyToken="c5687fc88969c44d" culture="neutral"/>
<bindingRedirect oldVersion="1.0.7.30072" newVersion="5.0.3.0"/>
</dependentAssembly>
</assemblyBinding>
The Fusion log is empty when logging all bind failures (I guess that means that all assemblies are loaded) and FileMon seems also to find the file:
14:32:06 dllhost.exe:436 OPEN C:\dev\src\new_efm\EFM_ComMiddlewareService\bin\De bug\MySql.Data.dll SUCCESS Options: Open Access: All
14:32:06 dllhost.exe:436 READ C:\dev\src\new_efm\EFM_ComMiddlewareService\bin\De bug\MySql.Data.dll SUCCESS Offset: 0 Length: 4096
14:32:06 dllhost.exe:436 READ C:\dev\src\new_efm\EFM_ComMiddlewareService\bin\De bug\MySql.Data.dll SUCCESS Offset: 128 Length: 512
14:32:06 dllhost.exe:436 CLOSE C:\dev\src\new_efm\EFM_ComMiddlewareService\bin\De bug\MySql.Data.dll SUCCESS
I still get the same exception as last time, and the exception says that second argument cannot be converted:
Cannot convert property value of type [System.String] to required type [System.Type] for property ''.
I have registered for AssemblyResolve as well, but since it succeeded in loading the assembly the event never got fired (at least nothing was appended to the event log).
static Assembly CurrentDomain_AssemblyResolve(object sender, ResolveEventArgs args)
{
EventLog.WriteEntry("EfmComService", "Warning = Failed to load Assembly: ["+args.Name+"]", EventLogEntryType.Warning);
return null;
}
Thanks for all input so far, the AppDomain.CurrentDomain events are really useful for debugging.
Cheers,
Steinar.
Erich Eichinger
02-13-2007, 03:32 PM
Hi,
since the .dll seems to get loaded (no fusion error, no AssemblyResolve event) I've got only 1 idea left: One of the types within the old version of mysql.data.dll has been dropped / moved namespace and can't be found.
I guess you need to create your own dbProvider as mentioned in my previous post until we add a current version to dbproviders.xml.
Sorry for the inconvenience!
-Erich
Mark Pollack
02-13-2007, 04:34 PM
Hi,
There are two drivers for mysql, the '1.0' series and the '5.0' series. The 1.0 series doesn't support new features introduced into ADO.NET 2.0 while the 5.0 series does. The dbproviders.xml file has info for the 1.x series, if you download the latest version of it, 1.0.9, it probably will work since it is just a point release. http://dev.mysql.com/downloads/connector/net/1.0.html I haven't looked but I'd bet that the 5.0 series has changed enough so that it doesn't work with the older 1.0 reflection-like information that is contained in dbproviders.xml
We certainly need to support the 5.0 series, I've added two JIRA items (1 (http://opensource.atlassian.com/projects/spring/browse/SPRNET-483),2 (http://dev.mysql.com/downloads/connector/net/1.0.html)) to keep track of the msql driver support. As Erich mentioned, you can always extend the current dbproviders.xml as described in the docs.
Hope this helps.
I have not looked deeply into MSMQ integration, so there maybe particular best practices, api quirks that need addressing that are different than JMS. The intention with the current TIBCO JMS integration is to move to an API like NMS (http://activemq.apache.org/nms.html) so we can support many more providers. It maybe relevant for you to look at my article that just got published on infoq (http://www.infoq.com/articles/jms-spring-messaging-interop) and translate it to fit your context. What is interesting is the comment to the article that says there is the start of a NMS binding for MSMQ.
Mark
steinard
02-13-2007, 10:57 PM
Hi!
I finally resolved the problem using MySql version 5.0.3.0. I haven't tested it much yet so there might be, as you said, some inconsistencies or other quirks hiding. At least the mappings in the domain assembly is now properly building the database (so <bindingRedirect> works!).
The error message:
Cannot convert property value of type [System.String] to required type [System.Type] for property ''.
Lead me to thinking that there might be some kind of property that hadn't been set on the MySql assembly, as I had compiled it myself yesterday. I therefor reverted back to the release version and I got this strange MySql.Data.resource.dll error that may hint at some kind of resource file expected to be found in a localization directory below \bin\debug. As I read on localization yesterday, I saw that they were using embedded resources to store localization data in the dll file. I therefore changed the properties on all the configuration files that were to be embedded into the serviced component's dll file to 'content' (and thus not embedding anything). And I guess this did the trick, at least it does not search for MySql.Data.resource.dll anymore (no traces of this even when I log all bindings in Fusion) and the service gets loaded successfully. Another thing, I manually specify in the component services window, on the serviced component's activation tab, the component root directory. In that directory the service expects to find the 'application.config' and 'application.manifest' files (they must be named exactly like that).
Finally, the hibernate session is now controlled by a subclass of LocalSessionFactoryObject. I'm ready to use the OSIV module and possibly use a session scope/guard controlled by the serviced component to close the sessions (or keep them open for more that one request if I like).
I guess looking at how the new middleware can support both ActiveMQ and web services is the next step of research on my agenda. But first com+ should work properly. Thanks for all help so far, I really appreciate working with spring.
Cheers,
Steinar.
Erich Eichinger
02-13-2007, 11:25 PM
Hi Steinar,
I'm really glad you finally could solve your problem. If you got MySql 5 up & running stable we would really appreciate if you could commit this back to us.
Regarding the OSIV module I will refactor the logic into a "ConversationScope" class for RC1 that may be used standalone or by using OSIV within webapplications.
if you have some feedback about subclassing LocalSessionFactoryObject I'd also like to know about it - I once tried to subclass LocalSessionFactoryObject myself and found it quite tricky.
cheers,
Erich
steinard
02-14-2007, 02:04 PM
Hi!
If you got MySql 5 up & running stable we would really appreciate if you could commit this back to us.
I'll keep you updated on anything I discover when it comes to trouble with MySql 5. I have not had any time yet to really test it. Do you have any thoughts on expected trouble areas? Currently we will use MS SQL but our opinion is that our customers should choose what kind of DB they like (and some have an Oracle policy).
Yes, that is true. It felt like I had to bend the subclass a little to make it conform to the override conventions defined in AfterPropertiesSet method. Personally I would like that method to be made virtual and the needed properties protected.
/// <summary>
/// Initialize the SessionFactory for the given or the
/// default location.
/// </summary>
public void AfterPropertiesSet()
{
// Create Configuration instance.
Configuration config = NewConfiguration();
if (this.dbProvider != null)
{
config.SetProperty(Environment.ConnectionString,
dbProvider.ConnectionString);
//TODO infer ConnectionDriver and ConnectionProvider from
//dbProvider?
}
if (this.hibernateProperties != null)
{
config.AddProperties(hibernateProperties);
}
if (this.mappingAssemblies != null)
{
foreach (string assemblyName in mappingAssemblies)
{
config.AddAssembly(assemblyName);
}
}
IResourceLoader resourceLoader = new ConfigurableResourceLoader();
if (this.mappingResources != null)
{
foreach (string resourceName in mappingResources)
{
config.AddInputStream(resourceLoader.GetResource(r esourceName).InputStream);
}
}
// Perform custom post-processing in subclasses.
PostProcessConfiguration(config);
// Build SessionFactory instance.
log.Info("Building new Hibernate SessionFactory");
this.configuration = config;
this.sessionFactory = NewSessionFactory(config);
}
Cheers,
Steinar.
Erich Eichinger
02-14-2007, 04:04 PM
Hi Steinar,
Do you have any thoughts on expected trouble areas?
No nothing special on my mind. But don't look for trouble - it will find you :rolleyes:
Currently we will use MS SQL but our opinion is that our customers should choose what kind of DB they like (and some have an Oracle policy).
My experience is that as soon as your database schema reaches a certain complexity, you can't switch database vendors just by changing the "dialect" engine. You'll most likely need to change your DAOs - that's what they are meant for anyway (at least somehow).
Personally I would like that method to be made virtual and the needed properties protected.
I'll give it a thought and combine it with my own experience. It's been a couple of months ago, so I can't fully remember the odds of deriving LocalSessionFactory now.
Awaiting your MySql 5 reports :),
Erich
steinard
02-23-2007, 12:22 AM
Hi again!
Yes, problems do appear without me searching for them!
After finally resolving all the configuration problems (thanks to you guys!) I've been stuck a couple of days on some strange behavior in my service component running as a real server. I've tried a couple of approaches to create a session scope guard, and ended up with simply using this suggested approach:
///
public SessionDTO Login(string userName)
{
OpenSessionInViewModule osiv = new OpenSessionInViewModule();
osiv.SessionFactory = appConfig.SessionFactory;
try
{
osiv.Open();
if(log.IsDebugEnabled) log.Debug("Retrieving user with username: "+userName);
IUser user = factory.UserManager.LoadUserByUserName(userName);
if (user != null)
{
...
return sessionDTO;
}
else throw new AuthenticationException("Incorrect username or password, please try again!");
}
finally { osiv.Close(); }
}
This works the first time I make a request to the server, but the second time, when I want to authenticate the typed password, by calling this method:
/// <summary>
/// Authenticate dirty session.
/// </summary>
/// <param name="dirty">the dirty session</param>
/// <returns></returns>
public SessionDTO Authenticate(SessionDTO dirty) {
if(log.IsDebugEnabled) log.Debug("Authenticate: Creating a new instance of OSIV");
OpenSessionInViewModule osiv = new OpenSessionInViewModule();
osiv.SessionFactory = appConfig.SessionFactory;
try
{
osiv.Open();
IUser user = factory.UserManager.LoadUserByUserName(dirty.UserN ame);
bool authenticated = sessionManager.Authenticate(user, dirty);
if (authenticated)
{
...
return new SessionDTO(sessionId);
}
else throw new AuthenticationException("Incorrect username or password, please try again!");
}
finally { osiv.Close(); }
}
A nullpointer is thrown when invoking this method:
factory.UserManager.LoadUserByUserName(dirty.UserN ame);
Then I get this error message:
Message: System.NullReferenceException, object not set to a reference....
Source: Spring.Data.NHibernate12
Server stack trace:
at Spring.Data.NHibernate.Support.OpenSessionInViewMo dule.LazySessionHolder.EnsureInitialized()
at Spring.Data.NHibernate.SessionHolder.get_IsEmpty()
at Spring.Data.NHibernate.SessionFactoryUtils.GetSess ion(ISessionFactory sessionFactory, IInterceptor entityInterceptor, IAdoExceptionTranslator adoExceptionTranslator, Boolean allowCreate)
at Spring.Data.NHibernate.SessionFactoryUtils.GetSess ion(ISessionFactory sessionFactory, IInterceptor entityInterceptor, IAdoExceptionTranslator adoExceptionTranslator)
at Spring.Data.NHibernate.HibernateAccessor.get_Sessi on()
at Spring.Data.NHibernate.HibernateTemplate.Execute(I HibernateCallback action, Boolean exposeNativeSession)
at Spring.Data.NHibernate.HibernateTemplate.Execute(I HibernateCallback action)
at Spring.Data.NHibernate.HibernateTemplate.Execute(H ibernateDelegate del)
at efm.persistence.nhibernate.dao.impl.UserDAO.LoadUs erByUserName(Type entityType, String userName) i C:\dev\src\new_efm\EFM_Persistence\efm\persistence \nHibernate\dao\impl\UserDAO.cs:linje 41
at efm.managers.impl.UserManager.LoadUserByUserName(S tring userName) i C:\dev\src\new_efm\EFM_Managers\efm\managers\impl\ UserManager.cs:linje 85
at Spring.Proxy.CompositionAopProxy_8a11ef6ec6074fa7a e52aab138ca1596.LoadUserByUserName(String userName)
at efm.com.service.EfmComService.Authenticate(VizSess ionDTO dirty) i C:\dev\src\new_efm\EFM_ComMiddlewareService\efm\co m\service\EfmComService.cs:linje 165
at System.Runtime.Remoting.Messaging.StackBuilderSink ._PrivateProcessMessage(IntPtr md, Object[] args, Object server, Int32 methodPtr, Boolean fExecuteInContext, Object[]& outArgs)
at System.Runtime.Remoting.Messaging.StackBuilderSink .PrivateProcessMessage(RuntimeMethodHandle md, Object[] args, Object server, Int32 methodPtr, Boolean fExecuteInContext, Object[]& outArgs)
at System.Runtime.Remoting.Messaging.StackBuilderSink .SyncProcessMessage(IMessage msg, Int32 methodPtr, Boolean fExecuteInContext)
The funny thing is that this only happens when the serviced component runs as a server. Looking at the code from HibernateAccessor all the way down to the OpenSessionInViewModule didn't give me any clues to what might be wrong (the EnsureInitialized method looked fine!) or what I'm doing wrong, so I decided to rebuild Spring, Spring.NHibernate and Spring.NHibernate12 in debug mode and sign the assemblies (I wish there were such a build target in spring.build).
So, after launching the serviced component with the pdb files hooked into VS and attaching the serviced component's process in debug mode, I finally was able to step in the spring code and see what happens on the server side when I try to retrieve the user the second time:
/// <summary>
/// Create a new session on demand
/// </summary>
protected override void EnsureInitialized()
{
if (session == null)
{
if (log.IsDebugEnabled) log.Debug( "session instance requested - opening new session" );
session = ownerModule.OpenSession();
AddSession( session );
}
}
And to my surprise, it's the ownerModule object that is null and triggers the NullReferenceException! Should this be possible? What am I doing wrong here?? Is it really safe to call:
finally { osiv.Close(); }
Are there perhaps other ways to close the hibernate session?
The only way I can see that the ownerModule object is set is through the LazySessionHolder's constructor in OSIVM:
public LazySessionHolder( OpenSessionInViewModule ownerModule )
{
if (log.IsDebugEnabled) log.Debug( "Created LazySessionHolder" );
this.ownerModule = ownerModule;
Current = this;
}
Should a newly created instance of the OpenSessionInViewModule be responsible for reinitializing the LazySessionHolder by providing itself as the ownerModule, or am I doing something very wrong to end up in this corner??
Thanks again,
Steinar.
steinard
02-25-2007, 11:15 AM
Hi!
I solved the problem, at least for my own implementation. I changed the OpenSessionInViewModule's Open method to prevent the ownerModule being null. I'm not sure if this fix will work for webapps but think it should.
By recompiling (debug mode) and signing the Spring assemblies I was able to attach VS to the server process and step in the Spring code on the server side. As my previous post stated, I found that the ownerModule object pointing back to the OpenSessionInViewModule was null, and this object were set to null by the module's close method. But Close does not remove the thread-bound reference to the resource LazySessionHolder from TransactionSynchronizationManager:
/// <summary>
/// Close the current view's session and unregisters
/// from spring's <see cref="TransactionSynchronizationManager"/>.
/// </summary>
public void Close()
{
if (!this.Participate)
{
if (this.SingleSession)
{
// single session mode
if (logger.IsDebugEnabled) logger.Debug( "Closing LazySessionHolder in OpenSessionInView" );
LazySessionHolder sessionHolder = LazySessionHolder.Current;
sessionHolder.Close();
}
else
{
// deferred close mode
if (logger.IsDebugEnabled) logger.Debug( "Closing all Hibernate Sessions" );
SessionFactoryUtils.ProcessDeferredClose( SessionFactory );
}
}
else
{
if (logger.IsDebugEnabled) logger.Debug( "Only participated Hibernate Session - doing nothing" );
}
}
And this method calls the LazySessionHolder's close method:
/// <summary>
/// Ensure session is closed (if any) and remove circular references to avoid memory leaks!
/// </summary>
public void Close()
{
ownerModule = null;
Current = null;
if (session != null)
{
ISession tmpSession = session;
session = null;
SessionFactoryUtils.CloseSession( tmpSession );
}
if (log.IsDebugEnabled) log.Debug( "Closed LazySessionHolder" );
}
So, on the second request, and the second time a new OpenSessionInViewModule instance has been created, then the Open method will check if there exists a session holder for current thread:
if( TransactionSynchronizationManager.HasResource( SessionFactory ))
{
...
Participate = true;
...
}
else
{
TransactionSynchronizationManager.BindResource( SessionFactory, new LazySessionHolder( this ) );
}
And here the HasResource method will return true for the given SessionFactory, and by following up with a GetResource I saw that the object returned is an instance of LazySessionHolder. By inspecting the LazySessionHolder object I see that the owningModule now is null, and this should not be the case as this will trigger a nullpointer in the EnsureInitialized(). To avoid this problem I changed the Open method like this:
/// <summary>
/// Opens a new session or participates in an existing session and
/// registers with spring's <see cref="TransactionSynchronizationManager"/>.
/// </summary>
public void Open()
{
this.Participate = false;
if (this.SingleSession)
{
// single session mode
if (TransactionSynchronizationManager.HasResource( SessionFactory ))
{
// Do not modify the Session: just set the participate flag.
if (logger.IsDebugEnabled) logger.Debug( "Participating in existing Hibernate SessionFactory" );
Participate = true;
// Added by Steinar Dragsnes:
// What is the bound value of the session holder? Is it null???
object value = TransactionSynchronizationManager.GetResource(Sess ionFactory);
if (value == null) value = new LazySessionHolder(this);
else if(value is LazySessionHolder)
{
LazySessionHolder sessionHolder = (LazySessionHolder) value;
sessionHolder.ReInitializeModuleOwner(this);
}
else { if(logger.IsDebugEnabled) logger.Debug("Trying to participate with sessionholder and module owner: "+value); }
// End of code modification...
}
else
{
if (logger.IsDebugEnabled) logger.Debug( "Creating LazySessionHolder in OpenSessionInView" );
TransactionSynchronizationManager.BindResource( SessionFactory, new LazySessionHolder( this ) );
}
}
else
{
// deferred close mode
if (SessionFactoryUtils.IsDeferredCloseActive( SessionFactory ))
{
// Do not modify deferred close: just set the participate flag.
Participate = true;
}
else
{
SessionFactoryUtils.InitDeferredClose( SessionFactory );
}
}
In Addition I added this method in LazySessionHolder:
/// <summary>
/// Reinitialize an existing instance of a LazySessionHolder by providing
/// an owner module. Calling the method has no effect if the session holder
/// is already initialized with a module.
/// </summary>
/// <param name="owner"></param>
public void ReInitializeModuleOwner(OpenSessionInViewModule owner)
{
if(ownerModule == null) ownerModule = owner;
}
These two modifications were the only two I needed to prevent the owning module from being null when EnsureInitialized method is called, and now the server returns a respons to the client for each of the requests as expected. I did not change or remove anything which is related to session closing, as I thought that may affect how other templates may collaborate and reuse a session.
I'm not sure if I'm breaking some Spring convention by providing a way for reinitializing the LazySessionholder, if so I would like to hear what I can do to avoid this problem and simultaneously avoid that the owning module is null.
Cheers,
Steinar.
Erich Eichinger
02-25-2007, 08:33 PM
Hi Steinar,
I'm really deeply thankful for you looking into this. And must apologize for a stupid mistake introduced by me...
The real problem is simply that I accidentially removed the call to TransactionSynchronizationManager.UnbindResource() from within Close():
if (this.SingleSession)
{
// single session mode
if (logger.IsDebugEnabled) logger.Debug( "Closing LazySessionHolder in OpenSessionInView" );
// the call below has been missing:
TransactionSynchronizationManager.UnbindResource( SessionFactory );
LazySessionHolder sessionHolder = LazySessionHolder.Current;
sessionHolder.Close();
}
cheers,
Erich
steinard
02-25-2007, 11:10 PM
Hi Erich!
At least I learned some more about how Spring manages the sessions. It was actually quite interesting to follow the instruction pointer around.
Unfortunately all the exiting stuff must wait for a while (1 or 2 weeks) now as I must join in on user interface coding and get back a 1 to 1 unit test coverage that has silently slipped a little this last week.
I'll report back if I get any trouble regarding sessions and the OpenSessionInView module.
Cheers,
Steinar.
vBulletin® v3.7.3, Copyright ©2000-2008, Jelsoft Enterprises Ltd.