0
\$\begingroup\$

The framework mix problem

I'm using two frameworks -- PingIdentity's SCIM 2 SDK, which uses Jackson, and Spring LDAP -- to deserialise a SCIM resource (i.e. JSON) to a Java object then write it to an LDAP directory, and the other way around. The trouble is, both frameworks want to annotate the fields of my Java data class and this is getting very messy. Is there a good design to split those foreign requirements on my class -- coming from the frameworks -- so they do not mix or even conflict in one class? I imagine this case of "JSON <-> object <-> storage" transfer is rather common...

Code example

Let's make my example more concrete with some code. First, here are the requirements on my class, say AppleSauce, imposed by the frameworks:

  1. Class AppleSauce must extend com.unboundid.scim2.common.BaseScimResource.
  2. Class AppleSauce must be annotated with @com.unboundid.scim2.common.annotations.Schema.
  3. Fields of AppleSauce that should appear in the JSON serialisation must be annotated with @com.unboundid.scim2.common.annotations.Attribute.
  4. Class AppleSauce must be annotated with @org.springframework.ldap.odm.annotations.Entry (not really a issue, but mentioned for completeness' sake).
  5. Fields of AppleSauce that should be written to LDAP attributes must be annotated with @org.springframework.ldap.odm.annotations.Attribute.
  6. AppleSauce must contain a field of type javax.naming.Name annotated with @org.springframework.ldap.odm.annotations.Id.
  7. Both SCIM 2 SDK and Spring LDAP want AppleSauce to be a mutable JavaBean with getters and setters for all fields relevant to them. I want AppleSauce to be an immutable value object.

This is what AppleSauce looks like with just one interesting field email (my real class has about 45 fields):

// imports omitted

@com.unboundid.scim2.common.annotations.Schema(id = "MY_SCHEMA_URN", name = "AppleSauce", description = "delicious stuff to go with cake")
@org.springframework.ldap.odm.annotations.Entry(objectClasses = { "inetOrgPerson", "organizationalPerson", "person", "top" }, base = "ou=sauces,dc=example,dc=org")
public class AppleSauce extends com.unboundid.scim2.common.BaseScimResource {
  @org.springframework.ldap.odm.annotations.Id javax.naming.Name dn;

  @com.unboundid.scim2.common.annotations.Attribute(description = "email address", multiValueClass = String.class, isRequired = true)
  @org.springframework.ldap.odm.annotations.Attribute(name = "mail", syntax = "1.3.6.1.4.1.1466.115.121.1.26{256}")
  private List<String> email;

  // getters and setter omitted
}

A possible solution

My initial thought is to extract an interface containing all getters (this also has the advantage of providing a read-only view of the class) and two classes implementing it, each containing the same fields with annotations for the respective frameworks. Something like this:

public interface AppleSauce {
  List<String> getEmail();
}

@com.unboundid.scim2.common.annotations.Schema(id = "MY_SCHEMA_URN", name = "AppleSauce", description = "delicious stuff to go with cake")
public class ScimAppleSauce extends com.unboundid.scim2.common.BaseScimResource implements AppleSauce {

  @com.unboundid.scim2.common.annotations.Attribute(description = "email address", multiValueClass = String.class, isRequired = true)
  private List<String> email;

  @Override
  List<String> getEmail() {
    return this.email;
  }
}

@org.springframework.ldap.odm.annotations.Entry(objectClasses = { "inetOrgPerson", "organizationalPerson", "person", "top" }, base = "ou=sauces,dc=example,dc=org")
public class LdapAppleSauce implements AppleSauce {
  @org.springframework.ldap.odm.annotations.Id javax.naming.Name dn;

  @org.springframework.ldap.odm.annotations.Attribute(name = "mail", syntax = "1.3.6.1.4.1.1466.115.121.1.26{256}")
  private List<String> email;

  @Override
  List<String> getEmail() {
    return this.email;
  }
}

The downside of this approach is that it requires extra code to convert instances of ScimAppleSauce to LdapAppleSauce (copying lots of fields) and vice-versa, because neither framework will correctly work with the AppleSauce type. I am not too worried about the getters/setters boilerplate thanks to Project Lombok annotations, but manually copying 45 fields is a very boring and error-prone perspective.

Bonus difficulty

Of course, there is more to this story...

  1. Fields of class AppleSauce also carry javax.validation annotations, adding to the mess on fields. Would putting those annotations on getters in the interface AppleSauce be a good idea?
  2. What if I need to serialise AppleSauce into a different JSON format like plain JSON without SCIM? There could be a third implementation of AppleSauce, JsonAppleSauce, but then the number of converters explodes.
  3. I don't like the mutability of JavaBeans and would very much prefer all objects to be immutable.

Is there a clean design to solve this "framework mix" problem?

\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Browse other questions tagged or ask your own question.