Issue Details (XML | Word | Printable)

Key: OSGI-245
Type: Bug Bug
Status: Resolved Resolved
Resolution: Fixed
Priority: Critical Critical
Assignee: Costin Leau
Reporter: Wouter de Vaal
Votes: 1
Watchers: 2
Operations

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

equals returns false on two different proxies to the same bean

Created: 19/Oct/07 09:30 AM   Updated: 05/Jun/08 03:05 AM  Due: 28/Oct/07
Component/s: CORE
Affects Version/s: 1.0 M3
Fix Version/s: 1.1 RC1

Time Tracking:
Not Specified

File Attachments: 1. Zip Archive proxy-equalsbug.zip (8 kB)

Issue Links:
Related


 Description  « Hide
Consider 3 bundles with app contexts A, B and C, with exposed
osgi:service beans x, y and z
they contain the follow osgi:reference's
B->x
C->x
C->y

y.colx contains a collection of beans with the type of x.
z has a x and y injected

Now in the code of bundle C, I have something like this in the code of
the class of z:
y.colx.contains(x).

Even though x in bundle C points to the exactly same bean as in B,
they will never be equal as they are both different proxies.
For clarification, let's call the proxies c(x) and b(x).
So: y.colx contains b(x), and the call in C will be
y.colx.contains(c(x)), which is false.

I have created three eclipse projects containing the bundles above. I will attach these to the bug.

 All   Comments   Work Log   Change History   FishEye   Builds      Sort Order: Ascending order - Click to sort in descending order
Wouter de Vaal added a comment - 19/Oct/07 09:34 AM
Check out the output from class C, I think this is unexpected behaviour.

Wouter de Vaal added a comment - 19/Oct/07 09:35 AM
Note: in the code I used some different names, but I think my point should be clear from the code.

Costin Leau added a comment - 22/Oct/07 06:43 AM
Wouter, I've commmited a fix for the issue along with some tests.
Note that for this to work, you either require the same proxy setup or you need the objects to implement equals.
Very important, make sure that the interface/classes used to create the proxy declare the 'equals' method otherwise the 'backing' equals method will not be called.

Wouter de Vaal added a comment - 31/Oct/07 12:34 PM
Hi Costin,

I've tried the same code as I attached to this bug with the latest rc1 snapshot, but I still get the same result, the two referenced beans are not equal. The object that is reference has hashCode/equals methods and they are not called. It is just simply the "A" class and it does not implement any additional interfaces..

Costin Leau added a comment - 31/Oct/07 01:16 PM
Take a look at the tests and the FAQ - it depends whether the hashCode/equals are proxied or not. If they aren't then then they are not called and thus the proxy infrastructure equality will apply.

Wouter de Vaal added a comment - 31/Oct/07 01:45 PM
I don't see any new FAQ entry online (where should I look?)

This is the class that is being proxied, it doesn't get any simpler than that, how else can I make sure equals/hash are exposed? They are public methods of a class that just extends Object and nothing else:

package a;

public class A {

private String a;

public void setA(String a) {
this.a = a;
}

@Override
public String toString() {
return a;
}

@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + ((a == null) ? 0 : a.hashCode());
return result;
}

@Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
final A other = (A) obj;
if (a == null) {
if (other.a != null)
return false;
} else if (!a.equals(other.a))
return false;
return true;
}

}

application context:
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:osgi="http://www.springframework.org/schema/osgi"
xmlns:util="http://www.springframework.org/schema/util"
xmlns:p="http://www.springframework.org/schema/p"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
                      http://www.springframework.org/schema/osgi http://www.springframework.org/schema/osgi/spring-osgi.xsd
                      http://www.springframework.org/schema/util http://www.springframework.org/schema/util/spring-util.xsd">



<bean id="a"
class="a.A" p:a="a">
</bean>

<osgi:service id="aOsgi"
ref="a"
interface="a.A" />

</beans>


referencing app context snippets:
<osgi:reference id="a" interface="a.A" cardinality="1..1" />

Wouter de Vaal added a comment - 16/Nov/07 07:47 AM
Could you please check out this issue again? AFAIK it is still broken.

In the example I attached and posted on, I updated the equals/hash on A so it uses the system identity. Also I added a simple system.out to see whether it is ever called.
@Override
public int hashCode() {
System.err.println("A HASH CALLED");
return System.identityHashCode(this);
}

@Override
public boolean equals(Object obj) {
System.err.println("A EQUALS CALLED");
if (this == obj)
return true;
if (obj == null)
return false;
final A other = (A) obj;
return hashCode() == other.hashCode();
}

 I tried the following afterPropertiesSet in the C bundle:

@SuppressWarnings("nls")
@Override
public void afterPropertiesSet() throws Exception {
A c = b.getCola().iterator().next();
System.out.println("I expect true here: " + b.getCola().contains(a));
System.out.println("Calling equals directly on the two a objects: " + a.equals(b.getCola().iterator().next()));
System.out.println("System ids: " + b.getCola().iterator().next().getServiceIdentity());
System.out.println("System ids: " + a.getServiceIdentity());
System.out.println("Calling the hashes: " + a.hashCode() + "--" + b.getCola().iterator().next().hashCode());

//get a and b through "regular" osgi references (activator finds the beans through the bean context)
A aa = Activator.getA();
B bb = Activator.getB();
System.out.println("I expect true here: " + bb.getCola().contains(aa));
System.out.println("Calling equals directly on the two a objects: " + aa.equals(bb.getCola().iterator().next()));
}

This is the output:I expect true here: false
Calling equals directly on the two a objects: false
System ids: 17855236
System ids: 17855236
Calling the hashes: 448141710--448141710
I expect true here: false
Calling equals directly on the two a objects: false

and I only get 1 systemout line for A EQUALS CALLED and HASHCODE twice (which is triggered by the calls on the osgi reference). So the only real time the equals/hash on A is called, is when I called tried to test whether a is in the collection of b, when I fetched them through osgi directly.

All in all, I don't think this bug should have been closed


Wouter de Vaal added a comment - 14/Feb/08 03:00 AM
Hi,

I have bumped into another real-life scenario:
Bundle a declares a javax.sql.DataSource service.
Bundle b has a service that uses JdbcTemplate with a proxy of that DataSource
Bundle c has a service that call the service in bundle b and uses a JdbcTemplate with another proxy to the same DataSource, these call are done within a single PlatformTransactionManager session (a DataSourceTransactionManage).
The transaction manager uses the DataSource object as a key to store DataSource-connection mappings, so it depends on object equality to pick up the correct connection within a transaction.
We did do a workaround on this by extending the DataSource interface with equals/hashcode methods and extended our used DataSource (jakarta commons dbcp BasicDataSource) and adding system identity implementation to the new subclass.
See all this in the following bug report related to the transaction manager: http://jira.springframework.org/browse/SPR-4461

We believe there can be no generic solution to the problem, because even when you add equals/hash to the exported interface, the equals implementation may only rely on information exposed through this interface, so no internal state can be used in equality.
However it may be nice do add something of an addendum describing these issues and suggesting sulutions as described above.

gengshaoguang added a comment - 26/Feb/08 07:34 PM
Hi, I have experienced simelar problem, and finally I found that same classes were packaged in more than one bundle, so as different classloaders return un-equal java.lang.Class instance to (in fact) same class.

After removing duplicate deployment, share these class through Export-Package:, then things go well.

Good Luck, yours Geng.

Michael Pilquist added a comment - 12/Mar/08 08:58 AM
I'm running in to the same problem but with JPA. Spring JPA uses the EntityManagerFactory as the key for the TransactionSynchronizationManager. This is problematic if you have the following bundle configuration:
 - Bundle 1: Contains and exports the EntityMangerFactory and JpaTransactionManager
 - Bundle 2: Imports EMF and JpaTxMgr and contains a repository and the PersistenceAnnotationBeanPostProcessor

At runtime, the JpaTransactionManager creates the transaction and binds an entity manager holder to the TransactionSynchronizationManager using the EntityManagerFactory as the key. But when the EntityManager proxy that is injected in to the repository goes to retrieve the entity manager holder, it can't find it due to the EntityManagerFactory instance being a proxy.

Costin Leau added a comment - 25/Apr/08 07:29 AM
Guys, M2 will address this problem by implementing InfrastructureProxy new in Spring 2.5.4. I'll commit the code today (note that Spring 2.5.4-SNAPSHOT will be used).

Costin Leau added a comment - 05/Jun/08 03:05 AM
Guys, since I haven't got any reports so far I'm closing the issue.
 If you still encounter issue or think there is something extra that we can try, please reopen it or raise another one.
Thanks,