Issue Details (XML | Word | Printable)

Key: OSGI-359
Type: Improvement Improvement
Status: Resolved Resolved
Resolution: Fixed
Priority: Critical Critical
Assignee: Costin Leau
Reporter: Sebastiaan van Erk
Votes: 2
Watchers: 2
Operations

If you were logged in you would be able to see more operations.
Spring OSGi

transaction manager uses DataSource as a key in a hashmap, but DataSource interface does not define equals/hashCode

Created: 13/Feb/08 11:59 AM   Updated: 05/Jun/08 03:06 AM
Component/s: CORE
Affects Version/s: 1.0
Fix Version/s: 1.1 RC1

Time Tracking:
Not Specified

Issue Links:
Related
 


 Description  « Hide
When exporting a DataSource as a service using spring-osgi the DataSource will get proxied. This will cause subsequent lookups for the active transaction to fail when the lookups occur using the different proxies of the DataSource.

The reason I'm reporting it here as a bug is that the DataSource interface does not define hashCode or equals, which actually means that the behavior of using a DataSource as a key in a Map is undefined.

I don't really see a good solution to this problem unfortunately, as the DataSource interface simply defines no way to determine if it's the same as another instance.

However, there is a workaround, namely to extend the DataSource interface to expose equals and hashCode and to define a EHDataSourceAdapter which implements these using the identity hash code of the adapter instance. Instead of using the raw data source in the application one would then use the wrapped data source (typically defined in the spring application context), giving a well-defined eqhash behavior and allowing it to be used as a map key.

With OSGI becoming more popular maybe something like this could be included somewhere as data source util classes, or maybe you guys can come up with a better solution. :-)

Issues linked to this one are:
http://static.springframework.org/spring-osgi/docs/1.0/reference/html/known-issues.html#OSGI-245
http://jira.springframework.org/browse/OSGI-245

The EHDataSource is just DataSource extended but equals and hashCode added. In EhDataSourceAdapter the important methods are equals and hashCode, the others just delegating to the real data source. The only problem with the EHDataSourceAdapter is that it implements JDK1.6 data source, so maybe it should be dynamic as Sun made incompatible changes to the interface. :-(( And perhaps a noarg constructor and setter should be included for people that do not like constructor injection.

public interface EHDataSource extends DataSource {

boolean equals(Object obj);

int hashCode();

}

public class EHDataSourceAdapter implements EHDataSource {

private final DataSource dataSource;

public EHBasicDataSource(DataSource dataSource) {
this.dataSource = dataSource;
}

@Override
public boolean equals(Object obj) {
if (!(obj instanceof EHDataSource)){
return false;
}
EHDataSource other = (EHDataSource) obj;
return other.hashCode() == hashCode();

}

@Override
public int hashCode() {
return System.identityHashCode(this);
}

public Connection getConnection() throws SQLException {
return dataSource.getConnection();
}

public Connection getConnection(String username, String password)
throws SQLException {
return dataSource.getConnection(username, password);
}

public int getLoginTimeout() throws SQLException {
return dataSource.getLoginTimeout();
}

public PrintWriter getLogWriter() throws SQLException {
return dataSource.getLogWriter();
}

public boolean isWrapperFor(Class<?> iface) throws SQLException {
return dataSource.isWrapperFor(iface);
}

public void setLoginTimeout(int seconds) throws SQLException {
dataSource.setLoginTimeout(seconds);
}

public void setLogWriter(PrintWriter out) throws SQLException {
dataSource.setLogWriter(out);
}

public <T> T unwrap(Class<T> iface) throws SQLException {
return dataSource.unwrap(iface);
}

}



 All   Comments   Work Log   Change History   FishEye   Builds      Sort Order: Ascending order - Click to sort in descending order
Juergen Hoeller added a comment - 14/Feb/08 12:57 PM
The proper way to fix this is to have the OSGi-exposed service proxies implementing equals/hashCode accordingly, considering proxies for the same target service as equal. Costin promised that he's going to check out what he can about it in Spring Dynamic Modules.

Juergen

Costin Leau added a comment - 21/Feb/08 02:52 AM
Thanks for reporting Sebastiaan. You raise a good point regarding actual object vs proxy equality which becomes obvious when dealing with collections.

The solution here is to let hte proxies implement the equals and hashcode by delegating to the target object. In Spring DM we'll probably do this automatically for the user or we'll provide a specific interface that can be added to the proxies so they can be distinguished from 'normal' proxies.
We'll probably add a flag to the schema so this can be turned off if not needed.

I've raised the priority of the issue but I'm not sure at the moment what's the ETA...

Sebastiaan van Erk added a comment - 21/Feb/08 04:23 AM
Thanks for looking in to it. :-)

I'm wondering about the solution though to let proxies implement equals and hashcode by delegating. Suppose you do proxy1.equals(proxy2). Then if proxy1.equals() delegates it will call delegate1.equals(proxy2), which will fail most of the time, because of the standard equals constructions which tests class equality (and delegate1.getClass() != proxy2.getClass()). Of course the equals method of the proxy could test if the argument is a known proxy (i.e., proxy2 instanceof the proxy1 class) and unwrap the delegate2 from it and use that in the delegated equals argument; this would of course only work if the same proxy class is always used but breaks as soon as there are 2 different eqhash delegating proxies for 1 class.

How would this work in an OSGI setting where 1 bundle exports a spring service, and different importing bundles each create their own dynamic proxy around the service? That is, bundle "a" exports service "a", bundle "b" imports it and creates $Proxy1(a) and bundle "c" creates $Proxy2(a)? (This is really what happens; the bundles each create their own proxy and thus have a different proxy class). There are several comparisons that must work in the end, the hairier ones being:
  a.equals($Proxy1(a)) // this will only work if a is proxied as well, i.e., no bare a is exposed
  $Proxy1(a).equals($Proxy2(a))
I guess having the dynamic proxy implement not only the service interface but also a special interface which has a getDelegate() method that can be used to get the delegate for the equals method can solve this problem, as long no bare (non-proxied) "a" can occur (i.e., in bundle "a" the service "a" is also proxied). Of course this might cause double proxies $Proxy1($Proxy0(a)) (i.e., the proxied service "a" imported in bundle "b"), which would mean that getDelegate() needs to be recursive (and so getDelegate() turns out to be a bad name, getProxyTarget() may be better or something like that).

Is something like this the solution you have mind?

Regards,
Sebastiaan

gengshaoguang added a comment - 26/Feb/08 07:52 PM
Hi :-)

My point is servicing a DataSource is not a good way, in my project, I servicing a hibernate's SessionFactory.

DataSource is usually leveraged by pooling, there are special mechanism for reclaiming objects.

Good Luck

Costin Leau added a comment - 25/Apr/08 07:30 AM
In M2 this problem will be addressed by implementing InfrastructureProxy new in Spring 2.5.4. I have the code working locally but I'm waiting for a proper snapshot artifact to be deployed.