Showing posts with label Generics. Show all posts
Showing posts with label Generics. Show all posts

Wednesday, June 11, 2014

"SQL like" Java Collections

In my daily work I do a lot of Java coding, but I also use SQL quite a lot.
I like SQL for its declarative nature. I think it is quite intuitive to spell out queries. In .NET there is LINQ which gives nice query capabilities. So I thought why not have similar API in Java - for querying collections. I tried to look for some existing libraries and found some. The closest solution I found was JoSQL. As stated in its website:
"JoSQL (SQL for Java Objects) provides the ability for a developer to apply a SQL statement to a collection of Java Objects. JoSQL provides the ability to search, order and group ANY Java objects and should be applied when you want to perform SQL-like queries on a collection of Java Objects."
JoSQL uses a Query object which gets a string with the SQL query. When it is called, the query is parsed and executed. I guess, but did not check, that reflection is used extensively there. It looks quite nice, but the main issue that I find with it is that it is error prone. The query validity is not enforced in design-time. If you have an error you will know it only in runtime, and there it is a matter of how much the error message is useful, and hopefully you caught it during development/QA.

With this issue I have decided to implement a different API which will give some query capabilities over collections and its validity will be evaluated in design-time. The main characteristics I chose for my API were:
  • Generics
  • Fluent API (i.e. method chaining)
  • Functional Programming
The library it-self is not complete, I did it only as a POC for myself. It is hosted on Google Code:
https://code.google.com/p/ynn-util-collections/wiki/SQLlikeCollections
Let's see some examples.
First, let's assume we have the following import statements:
import static ynn.util.collections.Predicates.*;
import static ynn.util.collections.SqlLikeCollections.*;
The first import statement imports some predefined static predicate methods such as "and", "or", "not", "lessThan", etc. The second import statement imports the entry point static methods for our queries.
Now that we have our basics imported we can write queries.

Examples


Example 1 -

Select all strings in a collection which are not null:
Collection<String> data = Arrays.asList(
    null, "A", "B", "C", null, "D", "E", "F", "G", "H", "I", null);
Collection<String> result = selectFrom(data).where(not(isNull()));
As you can see, this is quite a simple query. You can also notice that the result is typed and keeps the same type as the source (i.e. String). The syntax is quite similar to SQL, which would probably be something like:
SELECT * FROM "DATA" WHERE NOT "DATA"."VALUE" IS NULL

Let's examine some other more complex examples.

Example 2 -

In this example we will have a slightly more complex where clause and we'll also add some sorting. The API has some predefined predicates and utilities for Strings, so we'll import them as well:
import static ynn.util.collections.Predicates.StringPredicates.*;
Now for the example:
Collection<String> result = selectFrom(data)
    .where
        and(
            not(isNull()),
            stringStartsWith("A"),
            stringContains("a")
        )
    )
    .orderBy(LEXICOGRAPHIC_ORDER);
I this example you can see we select all strings which are not null, and it starts with "A", and it contains "a". The result is ordered by lexicographic order. Quite readable, right?

Of course the API is not limited to Strings or just simple objects. Let's see a more complex example.

Example 3 -

In this example we first need to define our Person class:
public class Person {

    private String name;
    private int age;
    private double weight;

    public Person() { }

    public Person(String name, int age, double weight) { 
        this.name = name;
        this.age = age;
        this.weight = weight;
    }

    // Getter & setter methods go here, I omitted them for simplicity
    // Also any other specific implementation (toString, ...)

    /*
     * VALUE PROVIDERS
     */

    public static ElementValueProvider<Person, String> name() {
        return new ElementValueProvider<Person, String>() {
            @Override
            public String getValue(Person element) {
                return element.getName();
            }
        };
    }
        
    public static ElementValueProvider<Person, Integer> age() {
        return new ElementValueProvider<Person, Integer>() {
            @Override
            public Integer getValue(Person element) {
                return element.getAge();
            }
        };
    }
	
}
You probably noticed the static methods under the "VALUE PROVIDERS" comment. Their purpose will be clearer in the example:
List<Person> data = Arrays.asList(
    new Person("John", 2, 2.2),
    new Person("David", 1, 1.1),
    new Person("Bob", 4, 4.4),
    new Person(null, 2, 7.7),
    new Person("Robin", 1, 3.3),
    new Person("Danny", 2, 5.5),
    new Person("Alice", 3, 6.6)
);

Collection<Person> result = 
    selectFrom(data)
    .where(
        not(isNull(Person.name())))
    .orderBy(
        desc(Person.age()), 
        asc(Person.name()));
As you can see above, we wrote a query for selecting all persons whom their name is not NULL. We then sorted the collection, first descending by age, then ascending by name. The corresponding SQL query would probably look something like:
SELECT * FROM "DATA"
WHERE NOT "PERSON"."NAME" IS NULL
ORDER BY "PERSON"."AGE" DESC, "PERSON"."NAME" ASC
Quite similar, right? And it's type safe as well!

One last example - I also started to play with the idea to support mass updates. See the example below.

Example 4 -

In this example we will update all persons with NULL name to have empty string for a name and their age to zero. For the example we need to add the following methods to our Person class:
    /*
     * UPDATERS
     */

    public static Updater<Person> nameTo(final String newName) {
        return new Updater<Person>() {
            @Override
            public void update(Person element) {
                element.setName(newName);
            }
        };
    }

    public static Updater<Person> ageTo(final int newAge) {
        return new Updater<Person>() {
            @Override
            public void update(Person element) {
                element.setAge(newAge);
            }
        };
    }
Now we can write our mass update statement:
update(data)
    .where(
        isNull(Person.name()))
    .set(
        Person.nameTo(""), 
        Person.ageTo(0));
See? nice, right? 

Conclusion

Of course there is a lot to extend, many capabilities missing, the performance of the current implementation can be easily improved, etc. This was a proof of concept. I think having such API can make developers' life easier, especially if they also write real SQL...
By-the-way - now that Java 1.8 is out, with all its new capabilities such as lambda expressions, method reference, functional interfaces, aggregate methods, streams, etc. It gives exactly what I was trying to accomplish here. The main difference is the syntax, but the usage is very similar.

Thursday, May 15, 2014

Using Java generics for proxy implementation

Overview

Java's generics is very powerful and easy to use. Its common use is straight forward, for example in the Collections API. In this post I would like to show another use, not so common, of generics. The purpose is to implement a proxy for remote class, e.g. when using RMI (Remote Method Invocation), JMX (Java Management Extensions), etc. This is handy since you usually have similar basic functionality in your proxy - connect, disconnect, etc.

Design

Class diagram of the participants in the design
The participants in the design - remote classes in green, local classes in blue (the interface is deployed remotely and locally)
As you can see in the class diagram above, there are 4 participants:
  • MyInterface -
    This is the interface of the remote class. This interface must be deployed both remotely and locally.
  • MyRemoteClass -
    This is the implementation of the remote class. It is deployed remotely and is the target for our proxy (in the client-side)
  • AbstractProxy -
    This is an abstract implementation of a proxy. It has some basic common proxy functionality such as connect, disconnect, etc. and it holds the actual proxy object encapsulated inside. This class is deployed locally (in the client-side).
  • MyProxy -
    This is the specific proxy for MyRemoteClass. It implements MyInterface and extends AbstractProxy setting the generic type to be MyInterface.

Implementation

Here is partial and simplified implementation of the classes in the design above.

MyInterface

public interface MyInterface {

    void doSomething();

}

MyRemoteClass

public class MyRemoteClass implements MyInterface {

    @Override
    public void doSomething() {
        // Do something...
    }

}

AbstractProxy

public abstract class AbstractProxy<T> {
 
    private T proxy;
 
    protected T getProxy() {
        return proxy;
    }
 
    public void connect() {
        // Connect to the remote class - set the encapsulated proxy object 
    }
 
    public void disconnect() {
        // Disconnect from the remote class - reset the encapsulated proxy object 
    }
 
    public boolean isConnected() {
        // Check whether the proxy is connected
        return true;
    }

}

MyProxy

public class MyProxy extends AbstractProxy<MyInterface> implements MyInterface {

    @Override
    public void doSomething() {
        // Delegate the call to the proxy
        getProxy().doSomething();
    }

}

Use Case Example

Here is a more realistic example of how I used this pattern (with some simplification and modification) for implementing an abstract proxy for JMX agents.

Background

(If you are not interested in the "why", you can skip to the Implementation part)

A few years ago, as part of a distributed system I was working on (school project...), I wanted to implement agents that will be deployed on the servers and listen for requests. This agents needed to answer 2 main requirements:

  1. Remote method invocation -
    The ability to connect from one machine to another and trigger actions on the remote server.
  2. Monitor the agents on the remote server -
    The ability to connect from a PC to the agent on the remote server and interact with it for monitoring, configuration, perform "manual" actions, etc.

In the beginning I chose Java RMI for the task. It's quite easy to use (in development), and once you run the server you're OK. But then, if you want to monitor it from a remote PC you need to develop a dedicated client UI for that. Therefore I looked for another technology that will give me similar capabilities (of Remote Method Invocation) using an API, but will also answer my other requirement with an existing UI (command line or graphical).
The answer I found was JMX (Java Management Extensions). Although this is not exactly the purpose of JMX, it was close enough and it answers my requirements. The UI for monitoring is JConsole (part of the SDK). It enables you to connect to a Java process (local or remote) and monitor its threads, memory, loaded classes, registered MBeans and more. In the MBeans section you can view & update the attributes each MBean exposes and invoke its methods. This is a simple generic UI which was quite enough for the requirements.

After implementing the MBeans I needed to implement the clients which will enable me to programmatically connect to the remote agents. When I started I noticed there is some basic code that I repeats itself in all clients, just with small differences. These are good candidates for refactoring.

Implementation


public abstract class AbstractJmxClient<MBeanInterface> {
    private MBeanInterface proxy = null;
    private JMXConnector conn = null;
    private MBeanServerConnection mbsc = null;

    // JMX properties which are used to connect to the registered MBean 
    private String jmxHost = null;
    private String jmxDomain = null;
    private String jmxKeyName = null;
    private String jmxKeyValue = null;
    private int    jmxPort = -1;

    // Constructor getting all the JMX properties...
    // Getters for all the JMX properties...

    protected MBeanInterface getProxy() {
        return _proxy;
    }

    /**
     * Connect to the remote JMX agent
     */
    @SuppressWarnings("unchecked")
    public void connect() throws Exception
    {
        String urlSuffix = null;
        try {
            urlSuffix = this.jmxHost + ":" + this.jmxPort + "/jmxrmi";
            ObjectName oName = new ObjectName(this.jmxDomain, this.jmxKeyName, this.jmxKeyValue);
            JMXServiceURL url = new JMXServiceURL("service:jmx:rmi:///jndi/rmi://" + urlSuffix);
            if (this.conn == null) {
                this.conn = JMXConnectorFactory.connect(url, null);
            }
            if (this.mbsc == null) {
                this.mbsc = this.conn.getMBeanServerConnection();
            }
            Type type = getClass().getGenericSuperclass();
            Class<MBeanInterface> mbiClass = null;
            if (type instanceof ParameterizedType) {
                ParameterizedType paramType = (ParameterizedType) type;
                mbiClass = (Class<MBeanInterface>) paramType.getActualTypeArguments()[0];
            }
            this.proxy = JMX.newMBeanProxy(this.mbsc, oName, mbiClass);
        } catch (RuntimeException e) {
            String errMsg = String.format(
                "Could not connect to the JMX agent: [%s:%s=%s] at %s due to the following error: %s",
                this.jmxDomain, this.jmxKeyName, this.jmxKeyValue, urlSuffix, e.getMessage());
            throw new Exception(errMsg, e);
        }
    }

    /**
     * Disconnect from the remote JMX agent
     */
    public void disconnect() {
        this.proxy = null;
        this.mbsc = null;
        if (this.conn != null) {
            try {
                this.conn.close();
            } catch (IOException e) {
                // Write to the trace
            }
            this.conn = null;
        }
    }

    /**
     * Check whether the client is connected to the remote JMX agent
     * 
     * @return true if the client is connected
     */
    public boolean isConnected() {
        if (this.proxy == null || this.conn == null) {
            return false;
        }
        try {
            this.conn.getConnectionId();
            return true;
        } catch (IOException e) {
            return false;
        }
    }

}