Skip to content

Structured map proxy implementation

License

Apache-2.0, Apache-2.0 licenses found

Licenses found

Apache-2.0
LICENSE
Apache-2.0
LICENSE.txt
Notifications You must be signed in to change notification settings

BlackBeltTechnology/structured-map-proxy

structured-map-proxy

badge

What is this library about?

It combines interface based proxies with java.util.Map backends. This means that you can define typesafe handlers to dynamic structures as Map and Collection of maps over the Map and Collection defined generics. It implements MapHolder interface also.

public interface MapHolder {
    Map<String, Object> toMap();
    Map<String, Object> getOriginalMap();
    <T> T adaptTo(Class<T> clazz);
}

It can handle nested interfaces too, so all types of structures can be defined with a structure of interfaces.

The map entries and interface methods are mapped as per the JavaBean standard specification. This results in for example: a String named as stringValue in a Map to have a corresponding interface containing getStringValue and setStringValue.

It also can be used to transform values from/to java beans. The MapHolder contains methods which can convert to map or adapt to beans. When the interface contains the given method the casting to the MapHolder not needed, the method can be called on the given interface directly.

Usage

Add the following to your pom.xml:

<dependency>
  <groupId>hu.blackbelt</groupId>
  <artifactId>structured-map-proxy</artifactId>
  <version>LATEST</version>
  <type>bundle</type>
</dependency>

Example:

In this example we have a User which can have a UserDetail as a single and containment collection.

Define getters/and setters in interfaces

public interface Identifier extends Serializable {
    @Key(name = "__id")
    Serializable getId();
    void setId(Serializable id);

    @Key(name = "__type")
    String getType();
    void setType(String id);
}


public interface Entity extends Serializable {
    @Embedded
    Identifier getId();
    void setId(Identifier id);

    @Embedded
    Identifier identifier();
}

public interface User extends Entity {
    Boolean getActive();
    void setActive(Boolean active);

    String getCredential();
    void setCredential(String credential);

    String getEmail();
    void setEmail(String email);

    Optional<String> getFirstName();
    void setFirstName(String firstName);

    @Key(name = "last_name")
    Optional<String> getLastName();
    void setLastName(String lastName);

    Optional<String> getLoginName();
    void setLoginName(String loginName);

    LocalDateTime getLastLoginTime();
    void setLastLoginTime(LocalDateTime lastLoginTime);

    UserDetail getSingleUserDetail();
    void setSingleUserDetail(UserDetail userDetail);

    Collection<UserDetail> getUserDetails();
    void setUserDetails(Collection<UserDetail> userDetails);
}

public interface UserDetail {
    @Embedded
    Identifier getId();
    void setId(Identifier id);

    String getNote();
    void setNote(String note);
}

Create a Proxy from a Map

// Create map represents fields.
Map<String, Object> prepared = new HashMap<>();
prepared.put("active", true);
prepared.put("__id", "1");
prepared.put("__type", "user");
prepared.put("email", Optional.of("[email protected]"));
prepared.put("loginName", Optional.of("teszt"));
prepared.put("lastLoginTime", Optional.of(time));
prepared.put("last_name", Optional.of("teszt"));
prepared.put("userDetails", ImmutableList.of(ImmutableMap.of("__id", "1", "__type", "UserDetail", "note", "Note1")));

User user = MapProxy.builder(User.class)
        .withMap(prepared)
        .newInstance();

user.setEmail("[email protected]");

Create a Map from a Proxy

// Create an empty proxy
User user = MapProxy.builder(User.class).newInstance();
user.setActive(true);
user.setLoginName("teszt");
user.setId(MapProxy.builder(Identifier.class).id("1").type("User").newInstance());

UserDetail userDetail1 = MapProxy.builder(UserDetail.class).newInstance();
userDetail1.setId(MapProxy.builder(Identifier.class).id("1").type("UserDetail").newInstance());
userDetail1.setNote("Note1");

UserDetail userDetail2 = MapProxy.builder(UserDetail.class).newInstance();
userDetail2.setId(MapProxy.builder(Identifier.class).id("2").type("UserDetail").newInstance());
userDetail2.setNote("Note2");

user.setUserDetails(ImmutableList.of(userDetail1, userDetail2));

Map<String, Object> mapRepresentation = ((MapHolder) user).toMap();

Not only Bean-type proxies are supported. Interfaces for builders can also be defined

Define builder interfaces

public interface UserBuilder {
    UserBuilder id(Serializable id);
    UserBuilder active(Boolean par);
    UserBuilder credential(String par);
    UserBuilder email(String par);
    UserBuilder firstName(String par);
    UserBuilder lastName(String par);
    UserBuilder loginName(String par);
    UserBuilder lastLoginTime(LocalDateTime par);
    UserBuilder userDetails(Collection<UserDetail> userDetails);
    UserBuilder singleUserDetail(UserDetail userDetail);

    User build();
}

Create a Proxy builder for a type

User user = MapBuilderProxy.builder(UserBuilder.class, User.class).newInstance()
     .id(MapProxy.builder(Identifier.class).id("1").type("User").newInstance())
     .active(true)
     .loginName("teszt")
     .build();

Create a bean from MapProxy

Define standard java beans with setters and getters.

@Builder
@Getter
@Setter
public class IdentifierBean {
    Serializable id;
    String type;
}

@Builder(builderMethodName = "entityBeanBuilder")
@Getter
@Setter
@AllArgsConstructor
@NoArgsConstructor
public class EntityBean {
    IdentifierBean compositeIdentifier;
    Serializable id;
}

@Getter
@Setter
@AllArgsConstructor
@NoArgsConstructor
public class UserBean extends EntityBean {

    String email;
    UserDetailBean singleUserDetail;
    Collection<UserDetailBean> userDetails;

    @Builder(builderMethodName = "userBeanBuilder")
    public UserBean(IdentifierBean compositeIdentifier,
                    Serializable id,
                    String email,
                    UserDetailBean singleUserDetail,
                    Collection<UserDetailBean> userDetails
                    ) {
        super(compositeIdentifier);
        this.email = email;
        this.singleUserDetail = singleUserDetail;
        this.userDetails = userDetails;
    }
}

@Builder
@Getter
@Setter
@EqualsAndHashCode
@AllArgsConstructor
@NoArgsConstructor
public class UserDetailBean {
    String id;
    String note;
}
User user = MapBuilderProxy.builder(UserBuilder.class, User.class).newInstance()
     .id(MapProxy.builder(Identifier.class).id("1").type("User").newInstance())
     .active(true)
     .email("teszt")
     .build();

UserBean bean = user.adaptTo(UserBean.class);

hashCode

You can define static hashCode method on the interface. On that case that method will be performed on the proxy’s object call. If not defined the toString method’s hashCode is called.

public interface A {
   String getFld();
   static int hashCode(A o) {
      o.getFld().hashCode();
   }
}

toString

You can define static toString method on the interface. On that case that method will be performed on the proxy’s object call. If not defined the fields are iterated and printed.

public interface UserDetail {
@Key(name = "__id")
String getId();

    void setId(String id);

    String getNote();

    void setNote(String note);

    static String toString(UserDetail o1) {
        return String.format("{ id: %s, note: %s }",
                Objects.toString(o1.getId(), "null"),
                Objects.toString(o1.getNote(), "null")
        );
    }
}

equals

You can define static equals method on the interface. On that case that method will be performed on the proxy’s object call. If not defined the toString method’s equals is called.

public interface UserDetail {
@Key(name = "__id")
String getId();

    void setId(String id);

    String getNote();

    void setNote(String note);

    static boolean equals(UserDetail o1, Object o2) {
        if (o2 == null) {
            return false;
        }
        if (UserDetail.class.isAssignableFrom(o2.getClass())) {
            return o1.getId().equals(((UserDetail) o2).getId());
        }
        return false;
    }

}

Supported options in MapProxy

Immutable option example

MapProxy.builder(User.class).withImmutable(true).newInstance();

MapProxy supports the following options:

  • immutable (default false) Adjusts the fields and collections in the created proxy as immutable, which results in all mutating operation calls triggering an IllegalStateException

  • nullSafeCollection (default false) If set to true and given a collection is not set in the map, an empty collection will be returned. Which means that the collection in the interface can never be null.

  • enumMappingMethod (default name) When an enum type is defined as a field, we need to define what type of representation is coded in the map.

  • mapNullToOptionalAbsent (default false) When set to true and a value is not present, the proxy will return with an Optional value where the .isPresent() check will result in false.

Supported options in MapBuilderProxy

The MapBuilderProxy’s builder supports all of the options of MapProxy with a couple of addition. For example:

Adding a Builder method prefix

User user = MapBuilderProxy.builder(UserBuilder.class, User.class).withBuilderMethodPrefix("with").newInstance()
     .id("1")
     .active(true)
     .loginName("teszt")
     .build();
  • builderMethodPrefix (default false) It defines whether the buidler method can have a prefix or not. By default the builder method names match with the field name. With this option you can rename them.

  • enumMappingMethod (default name) When an enum type is defined as a field, we need to define what type of representation is coded in the map.

Annotations

There are annotations which helps to configure the mapping between Map and the interface. The annotations have to be defined in the getter method.

@Key

This annotation is used to define the key name in map for the related field.

@Embedded

Sometimes we need to store information in a hierarchical structure with or without dedicated interfaces. For such cases we can use the @Embedded annotation. The @Embedded annotation can be used on methods which are not getters for factory like functions.

Annotation examples

public interface Identifier extends Serializable {
    @Key(name = "__id")
    Serializable getId();
    void setId(Serializable id);

    @Key(name = "__type")
    String getType();
    void setType(String id);
}


public interface Entity extends Serializable {
    @Embedded
    Identifier getId();
    void setId(Identifier id);

    String getName();
    void setName(String name);

}

In this example the Entity’s map will contain type and id fields with name. So in the Map it is flattened, bu8t from MapProxy it can be accessed as an embedded MapProxy.

System properties

For better performance some fields and methods are cached. The cache eviction time can be set as system property with -D.

structuredMapProxyCacheExpireInSecond is 60 by default.

Contributing to the project

Everyone is welcome to contribute to structured-map-proxy! As a starter, please read the corresponding CONTRIBUTING guide for details!

License

This project is licensed under the Apache License 2.0.

About

Structured map proxy implementation

Resources

License

Apache-2.0, Apache-2.0 licenses found

Licenses found

Apache-2.0
LICENSE
Apache-2.0
LICENSE.txt

Stars

Watchers

Forks

Packages

No packages published

Languages