Monday 20 May 2013

Id column of viewmodel not bound on Postback in MVC

This weekend I had an issue with the postback of a simple viewmodel object. For some reason the validation of this object failed even though the Id was not required.
     public class SkillViewModel
    {
        public int Id { get; set; }

        [Required]
        public virtual string Description { get; set; }
        [Required]
        public virtual string Name { get; set; }
    }
      [HttpPost]
        public ActionResult Create(SkillViewModel skill)
            if (ModelState.IsValid)
            {
                SkillRecord oRecord = new SkillRecord()
                {
                    Name = skill,
                    Description = skill.Description
                };

                SkillService.Create(oRecord);

                return RedirectToAction("Index");
            }

            return View(skill);
        }
To solve this issue, I started by adding some debugging code to the controller action to find out what was going wrong.
var errors = ModelState.SelectMany(e => e.Value.Errors);
This gave me the following error: "The Id field is required.". This is strange. Of course changing the name of the Id property works, but this is not what I want. So what is going on. In the create I am underposting, this means I am not posting the Id. This gave me problems in combination with my defined routes.
routes.MapRoute(
                name: "Default",
                url: "{controller}/{action}/{id}",
                defaults: new { controller = "Home", action = "Index", id = UrlParameter.Optional }
            );
Since I am not posting the Id from by razor view, the binding takes tries to get the id from the route, which is not there and cause a validation error. There are two solutions, either add '[Bind(Exclude = "Id")]' before the parameter in the Create method or add an additional route (before the existing one) without the id in it:
routes.MapRoute(
                name: "Default",
                url: "{controller}/{action}",
                defaults: new { controller = "Home", action = "Index", id = UrlParameter.Optional }
            );

routes.MapRoute(
                name: "Default",
                url: "{controller}/{action}/{id}",
                defaults: new { controller = "Home", action = "Index", id = UrlParameter.Optional }
            );

Thursday 18 October 2012

Spring, Hibernate and multiple datasources. What about the cache?

I recently ran in to an issue where I needed to be able to select a datasource for use with Hibernate. After some searching on the web I found this Dynamic Datasource Routing by Mark Fisher. It turns out that selecting a datasource is actually quite simple. Just create a subclass from AbstractRoutingDataSource as described in the the blog post and you are able to select the datasource. 

This is a very elegant solution and it works like a charm, until.... you start using cache. Because the Hibernate session factory assumes you are using a single datasource it also assumes that the cache is for this datasource. Entities and collections stored by there classname and the entity id. This will result in cache collision between values from different datasources.

One of the comment suggests wrapping the cache in a wrapper class. But this is a problem in a versions of Hibernate where the CacheProvider is deprecated (3.3 and later). In my opinion a more elegant solution is to use AspectJ to define an aspect that intercepts the call to the EntityRegionAccessStrategy and CollectionRegionAccessStrategy interfaces. By substituting the CacheKey parameter by a my own DatabaseDependentCacheKey I am able to prevent potential cache collisions.

The aspect I used is fairly simple if you understand AspectJ. What is done in this aspect is that all interface methods where an CacheKey is passed are defined as pointcuts. Then I define multiple around advices where I wrap the original key in my DatabaseDependentCacheKey.
Since this happens for all action (insert, update, delete evict) no collisions can occur.
The aspect below is for the hibernate 3.6.9. In Hibernate 4 the interfaces have changed slightly.
package com.fennek.gem.datasource.lookup;

import org.aspectj.lang.JoinPoint;
import org.hibernate.cache.CacheKey;
import org.hibernate.cache.access.CollectionRegionAccessStrategy;
import org.hibernate.cache.access.EntityRegionAccessStrategy;
import org.hibernate.cache.access.SoftLock;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public aspect CacheKeyModifierAspect 
{
 private static final Logger log = LoggerFactory.getLogger( CacheKeyModifierAspect.class );
 
 pointcut afterInsertEntity(Object key): call(boolean EntityRegionAccessStrategy+.afterInsert(Object, ..)) && args(key, ..);
 pointcut afterUpdateEntity(Object key): call(boolean EntityRegionAccessStrategy+.afterUpdate(Object, ..)) && args(key, ..);
 pointcut evictEntity(Object key): call(void EntityRegionAccessStrategy+.evict(Object)) && args(key);
 pointcut getEntity(Object key): call(Object EntityRegionAccessStrategy+.get(Object, ..)) && args(key, ..);
 pointcut insertEntity(Object key): call(boolean EntityRegionAccessStrategy+.insert(Object, ..)) && args(key, ..);
 pointcut lockItemEntity(Object key): call(SoftLock EntityRegionAccessStrategy+.lockItem(Object, ..)) && args(key, ..);
 pointcut putFromLoadEntity(Object key): call(boolean EntityRegionAccessStrategy+.putFromLoad(Object, ..)) && args(key, ..);
 pointcut removeEntity(Object key): call(void EntityRegionAccessStrategy+.remove(Object)) &&  args(key);
 pointcut unlockItemEntity(Object key): call(void EntityRegionAccessStrategy+.unlockItem(Object, ..))  && args(key, ..); 
 pointcut updateEntity(Object key): call(boolean EntityRegionAccessStrategy+.update(Object, ..)) && args(key, ..);

 pointcut evictCollection(Object key): call(void CollectionRegionAccessStrategy+.evict(Object)) && args(key);
 pointcut getCollection(Object key): call(Object CollectionRegionAccessStrategy+.get(Object, ..)) && args(key, ..);
 pointcut lockItemCollection(Object key): call(SoftLock CollectionRegionAccessStrategy+.lockItem(Object, ..)) && args(key, ..);
 pointcut putFromLoadCollection(Object key): call(boolean CollectionRegionAccessStrategy+.putFromLoad(Object, ..)) && args(key, ..);
 pointcut removeCollection(Object key): call(void CollectionRegionAccessStrategy+.remove(Object)) &&  args(key);
 pointcut unlockItemCollection(Object key): call(void CollectionRegionAccessStrategy+.unlockItem(Object, ..))  && args(key, ..); 
 
 boolean around(Object key):
  afterInsertEntity(key) || 
  afterUpdateEntity(key) ||
  insertEntity(key) || 
  putFromLoadEntity(key) || 
  updateEntity(key) ||
  putFromLoadCollection(key){
  Object args = handleCacheKey(key, thisJoinPoint);
  return proceed(args);
 }
 
 void around(Object key):
  evictEntity(key)||
  removeEntity(key) ||
  unlockItemEntity(key){
  Object args = handleCacheKey(key, thisJoinPoint);
  proceed(args);
  return;
 }
 
 void around(Object key):
  evictCollection(key) ||
  removeCollection(key) ||
     unlockItemCollection(key){
  Object args = handleCacheKey(key, thisJoinPoint);
  proceed(args);
  return;
}
 
 Object around(Object key):
  getEntity(key)||
  getCollection(key) ||
  lockItemEntity(key)||
  lockItemCollection(key){
  Object args = handleCacheKey(key, thisJoinPoint);
  return proceed(args);
 }

 
 Object handleCacheKey(Object key, JoinPoint thisJoinPoint)
 {
  if (key instanceof CacheKey)
  {
   CacheKey cacheKey = (CacheKey)key;
   DatabaseDependentCacheKey oKey = new DatabaseDependentCacheKey(cacheKey, CustomerContextHolder.getCustomerType());
   log.debug(oKey.toString());
   return oKey;
  }
  else
  {
   log.debug(key.toString());
   return key;
  }
 }
}

package com.fennek.gem.datasource.lookup;

import java.io.Serializable;

import org.hibernate.cache.CacheKey;

public class DatabaseDependentCacheKey implements Serializable {

 private static final long serialVersionUID = 1L;
 
 private String dbKey; 
 private CacheKey cacheKey; 
 
 public DatabaseDependentCacheKey(CacheKey oKey, String DBKey){
  this.cacheKey = oKey;
  this.dbKey = DBKey;
 }
 
 @Override
 public boolean equals(Object other) {
  if ( !(other instanceof DatabaseDependentCacheKey) ) return false;
  DatabaseDependentCacheKey that = (DatabaseDependentCacheKey) other;
  return dbKey.equals( that.dbKey )
    && cacheKey.equals(that.cacheKey);
 }
 
 @Override
 public int hashCode() {
  return 31 * dbKey.hashCode() + cacheKey.hashCode();
 }

 public String getDatabaseKey() {
  return dbKey;
 }
 
 @Override
 public String toString() {
  return dbKey + '.' + cacheKey.toString();
 }
}

There is still one thing to do. Normally aspects are woven into the classes at compile time. Since we generally only use the compiled code (jar files) we have to set up Load Time Weaving. But this is a blog post by itself.

Monday 15 October 2012

CADRG support for Geoserver

I just found out that the latest version of geoserver is released. I waited long for this version because it contains my contribution to this project. The contribution consisted of a modification which allows the use CADRG files without converting it to another format. Just install the GDAL extensions for Geoserver and you are able to use these files.

Spring, JPA and Hibernate. How to access the Session Factory.

I am currently working on a Spring application with JPA and Hibernate. After adding cache on the relations I started to experience problems with the many-to-many relations. The reason for this is that these relations are not updated bi-directionally as shown below.

public class B {
    @ManyToMany
    private List listA;

    public void addToListA(A a) {
        this.listA.add(a);
        a.getListB().add(this);
    }

    public void removeFromListA(A a) {
        this.listA.remove(a);
        a.getListB().remove(this);
    }
}
In my case this turns out to be unpractical since I use Spring Roo to scaffold the application. In my generated controller I get the collection of new values that is assigned in one go to the corresponding property. So what to do? I choose to invalidate the collection cache for the specific object. But there is a slight catch. JPA is configured by Spring to use Hibernate. This is all done through the spring XML configuration files and there is no direct access to the hibernate SessionFactory from code. But there is a trick. Add the following bean to the applicationContext.xml file where the JPA entityManager is defined.
  
       
   

This will initialize the static fields of the following class.
package com.fennek.sessionexposer;

import java.io.Serializable;

import org.hibernate.ejb.HibernateEntityManagerFactory;
import org.hibernate.Cache;
import org.hibernate.SessionFactory;

import javax.persistence.EntityManagerFactory;


public class HibernateSessionFactoryService {
 static SessionFactory oFactory;
 
    public static SessionFactory setEntityManagerFactory(EntityManagerFactory hemf) {
     if (hemf != null){
      oFactory = ((HibernateEntityManagerFactory)hemf).getSessionFactory();
     }
     return oFactory;
    }
    
    public static SessionFactory getSessionFactory()
    {
     return oFactory;
    }
    
    public static void Evict(String entityName, Serializable id)
    {
     if (oFactory != null){
      Cache cache = oFactory.getCache();
      if (cache != null){
       cache.evictEntity(entityName, id);
      }
     }
    }
    
    public static void EvictCollection(String roleName, Serializable ownerId)
    {
     if (oFactory != null){
      Cache cache = oFactory.getCache();
      if (cache != null)
      {
       cache.evictCollection(roleName, ownerId);
      }
     }
    }
}
Now you have access to the Hibernate SessionFactory through the HibernateSessionFactoryService class. This allows you to invalidate the cache, but also gives you access to the Hibernate statistics.

Monday 20 June 2011

The dreaded Unspecified Error '80004005'. IIS7, OleDB and MS Access

Today it happened again. We were testing an ASP.NET website running under IIS 7 on W2K8 64bit. The website uses Windows authentication. The OleDB driver could connect happily to the MSAccess using the Microsoft.ACE.OLEDB.12 driver when using the browser from an account on the server, however connecting from a different client using Windows credentials failed with the dreaded Unspecified Error '80004005' OleDBException. This terrible unspecific error could mean anything.
After checking all kinds of settings and coding errors I decided to start a Coder's best friend, PROCMON (Process Monitor). I am always somewhat overwhelmed by the hundreds of thousands messages that show up. After filtering on process name (w3wp.exe) became obvious what was the problem. Just before the exception is thrown it shows ACCESS DENIED on directory c:\windows\ServiceProfiles\NetworkService\AppData\Local\Temp. Giving all users read/write access to this directory solved the problem.
I never saw this behaviour in IIS6. The reason is that in IIS6 the driver writes in c:\windows\temp and by default all users have write access on this directory. In IIS7 the temp directory for the NETWORK SERVICE account is no longer c:\windows\temp but c:\windows\ServiceProfiles\NetworkService\AppData\Local\Temp and only NETWORK SERVICE has access to this directory. When using windows authentication impersonation is used and an other user than NETWORK SERVICE tries to write in the directory and an exception is thrown.

Sunday 12 June 2011

Geoserver and CADRG RPFTOC support

I always wanted to start a blog, but always thought that the things I am doing, learning or discovering are trivial. I am interested in many things. One of the things I like is Geoserver. It is easy to setup and adding new data stores and layers is easy. For me the biggest lack in functionality is the support for the RPF TOC format. GDAL supports this format, however the software layers between GDAL and Geoserver do not have the required functionality. The documentation claims it is is easy to add the missing functionality by implementing a few classes and actually it is really not that hard. I managed to do this in a couple of hours. The patches are submitted to the imageio-ext project (patch 1) and the Geotools project (patch 2), so I expect this functionality to end up in Geoserver in the near future. To my surprise no changes where necessary to Geoserver and the functionality was available after deploying the jar files to the right locations. My compliments to the Geoserver team.
Update: The patch has been committed to imageio-ext and will appear in geoserver. My guess is that is will be in the next version of geoserver (2.2). If you want to try out this new functionality in version 2.1.0 then you can download unsupported patched jar files (download). Install your geoserver 2.1.0 with GDAL support and install the jar files in webapps/geoserver/WEB-INF/lib. Make sure you replace gt-imageio-ext-gdal-2.7.1.jar with gt-imageio-ext-gdal-2.7.1.modified.jar.