Oracle IDM API classes . . . few thoughts . . .

This is w.r.t one of the most frequently used value object classes which is a part of Oracle IDM API – the oracle.iam.identity.usermgmt.vo.User class (you can always peek into the javadocs available here)

 

iloveapis

 

What I noticed was that this VO class does not override the equals and hashCode methods from the Object class… It uses them as is  . . . (forgive me for posting this in case you knew it and found it too trivial ! 😉 )

What does this mean ?

  • The User class uses the equals method implementation defined in the Object class itself which decides equality based on the whether or not the two references being compared point to the same instance. This essentially means that two User objects can only be deemed equal if they point to the same memory location on the heap i.e the below will always return false

 


User user1 =  um.getDetails(UserManagerConstants.AttributeName.USER_LOGIN.getName(), "XELSYSADM", null);

User user2 =  um.getDetails(UserManagerConstants.AttributeName.USER_LOGIN.getName(), "XELSYSADM", null);

System.out.println("Are the two Users the same? "+user1.equals(user2));

 

 

  • The hashCode method in the Object class is a native implementation which traditionally works by converting the internal (heap) address of the object into an Integer. Because the User class just uses this implementation, its clear that two User objects will never have the same hash code even if they are conceptually the same (same User Login etc) i.e. the below code snippet will always return different values

 


User user1 =  um.getDetails(UserManagerConstants.AttributeName.USER_LOGIN.getName(), "XELSYSADM", null);

User user2 =  um.getDetails(UserManagerConstants.AttributeName.USER_LOGIN.getName(), "XELSYSADM", null);

System.out.println("Hash Code for User1 "+ user1.hashCode());
System.out.println("Hash Code for User2 "+ user2.hashCode());

 

You might be thinking . . what’s the big deal?!

Here is the thing – the above presented facts are not always going to cause an issue. For instance, one can argue that instead of using the equals method to compare two Users we can just compare them by their User Login or User Key (obtained by the getEntity() method) – this is absolutely correct

But there are certain scenarios (which are pretty obvious to be honest) where you will face problems – let’s look at one of them. . .

For instance, you have a HashMap wherein you have used instances of User objects as the key.

  • Assume that you either build this HashMap or obtain this form another part of your API or some external client code
  • If you have a User instance using which you would want to extract the values from the aforementioned Map – you won’t be able to do it. Try to execute the below code snippet in your OIM environment

 


	public Map<User, Date> giveMeAMapOfUsers() {

		Map<User, Date> map = new HashMap<>();

		try {
			UserManager um = oimclient.getService(UserManager.class);

			User user1 = um.getDetails(
					UserManagerConstants.AttributeName.USER_LOGIN.getName(),
					"XELSYSADM", null);

			Date dateOfCreation = new Date();

			map.put(user1, dateOfCreation);

		} catch (Exception e) {
			e.printStackTrace();
		}

		return map;
	}

	public void accessMapOfUsers(Map<User, Date> mapOfUsers) {

		try {
			UserManager um = oimclient.getService(UserManager.class);

			User user1 = um.getDetails(
					UserManagerConstants.AttributeName.USER_LOGIN.getName(),
					"XELSYSADM", null);

			Date dateOfCreation = mapOfUsers.get(user1);

			if (dateOfCreation != null) {
				System.out.println("My user was created on "
						+ dateOfCreation.toString());
			} else {
				System.out.println("??? null Date ???");
			}

		} catch (Exception e) {
			e.printStackTrace();
		}

	}

TestObj obj = new TestObj();
obj.accessMapOfUsers(obj.giveMeAMapOfUsers());

 

You will notice that in spite of trying to search the HashMap with the same user (a different reference variable of course), the result is null – since the key (User instance) could not be found.

This result will not be surprising for folks who are aware of how Hash based collections work. A detailed explanation is not the point of this post 😉

This is happening because the User class is using the hashCode implementation from the Object class (as stated above). The HashMap search algorithm works on the basis of hash codes based on which it looks into its internal ‘buckets’. So, the point really is, one cannot use User object as keys of a HashMap.

Is there a way out? Of course !!! 🙂

For such scenarios, a possible work around is to create a sub class of the User class, override the equals and hashCode methods as per your requirements and utilize this.

 


public class MyUser extends User{

	public MyUser(String entityId) {
		super(entityId);
	}

	@Override
	public boolean equals(Object aUser){
		if(aUser == null){
			return false;
		}

		User anotherUser = (User) aUser;

		if(anotherUser == this){
			return true;
		}

		if(this.getEntityId().equals(anotherUser.getEntityId())){
			return true;
		}

		return false;
	}

	@Override
	public int hashCode(){
		return new Integer(super.getEntityId()).intValue();

	}

}

 

If you execute the above code snippets using this sub class, they will execute as expected i.e. obey the equals and hashCode contracts

 


User user1 =  um.getDetails(UserManagerConstants.AttributeName.USER_LOGIN.getName(), "XELSYSADM", null);
User user2 =  um.getDetails(UserManagerConstants.AttributeName.USER_LOGIN.getName(), "XELSYSADM", null);

MyUser myUser1 = new MyUser(user1.getEntityId());
MyUser myUser2 = new MyUser(user2.getEntityId());

System.out.println("Are the two Users the same? "+myUser1.equals(myUser2));

 

Will return true


	public Map<User, Date> giveMeAMapOfUsers() {

		Map<User, Date> map = new HashMap<>();

		try {
			UserManager um = oimclient.getService(UserManager.class);

			User user1 = um.getDetails(
					UserManagerConstants.AttributeName.USER_LOGIN.getName(),
					"XELSYSADM", null);

			Date dateOfCreation = new Date();

			MyUser myUser1 = new MyUser(user1.getEntityId());

			map.put(myUser1, dateOfCreation);

		} catch (Exception e) {
			e.printStackTrace();
		}

		return map;
	}

	public void accessMapOfUsers(Map<User, Date> mapOfUsers) {

		try {
			UserManager um = oimclient.getService(UserManager.class);

			User user1 = um.getDetails(
					UserManagerConstants.AttributeName.USER_LOGIN.getName(),
					"XELSYSADM", null);

			MyUser myUser1 = new MyUser(user1.getEntityId());

			Date dateOfCreation = mapOfUsers.get(myUser1);

			if (dateOfCreation != null) {
				System.out.println("My user was created on "
						+ dateOfCreation.toString());
			} else {
				System.out.println("??? null Date ???");
			}

		} catch (Exception e) {
			e.printStackTrace();
		}

	}

TestBench obj = new TestBench();
obj.accessMapOfUsers(obj.giveMeAMapOfUsers());

 

Will return the java.util.Date object

 

There are many other instances in the OIM API classes which make our code a little too verbose at times

Few scenarios

  • What if I need to find out users provisioned on a specific date using the SearchCriteria class which the search method employs – not straightforward. I had to use a custom date class wherein the date had to be formatted and was forced to check the provisioned date for each user . . . phew !
  • What if I need to know whether an application instance is provisioned to a user? Yes – the obvious answer is to find out the provisioned instances of each user, iterate through them and match against your desired application instance name – is it ideal? How many object would you end up creating? How many queries would you fire when you use the ProvisioningService API ? Is the code performant ?? I doubt it….
  • Wouldn’t it have been nice of the oracle.iam.provisioning,vo.Account class (and other related classes as well) would have implemented java.lang.Comparable ?? Get them as a list, use the contains method – job done!
  • Have you noticed that the NotificationResolver interface is not available as a part of the client API i.e. oimclient.jar  . . . Not everyone will know where its located (at least without wasting some time 😉 ). It’s buried in OIMServer.jar !

 

This was just a teaser folks 😉 That’s all for now. . .

Cheers!

Advertisements

About Abhishek

Java EE & distributed systems junkie who frequently blogs at abhirockzz.wordpress.com as well as simplydistributed.wordpress.com. Oh, I have also authored a few (mini) books, articles, Refcards etc. :-)
This entry was posted in Java, Oracle Identity Governance, Oracle Identity Manager and tagged , , . Bookmark the permalink.

2 Responses to Oracle IDM API classes . . . few thoughts . . .

  1. Thank you for sharing your findings. Please fix your code snippets as they are bit corrupted: no “” symbols

    Like

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s