Aditya

Complete Java App

Complete Java App

This post describes how to create a completely scalable Java Application with Frontend included.

Steps:

  1. Use glassfish jersey maven archetype project (2.* with tomcat<10 & 3.*>= with tomcat>=10), preferably webapp one.
  2. Add following dependencies/plugins for local deployment:
    • ‘tomcat7-maven-plugin’ to quickly deploy war to tomcat using maven
    • ‘maven-war-plugin’ for getting updated war building functionality in maven
  3. ‘jersey-mvc-jsp’ & ‘jersey-media-multipart’ for getting jsp support in jersey
  4. Setup tomcat maven plugin by adding a user with all functionality in tomcat-users.xml and add that in maven settings.xml also. Use following steps to do so:
    • First add user with support to access manager in tomcat. Example code:
      1
      2
      3
      4
      5
      6
      7
      
        <tomcat-users>
            <role rolename="manager-gui"/>
            <role rolename="manager-script"/>
            <role rolename="manager-jmx"/>
            <user username="admin" password="tomcat" roles="manager-gui"/>
            <user username="maven" password="tomcat" roles="manager-jmx,manager-script"/>
        </tomcat-users>
      
    • Then add the support for this user in maven settings.xml. Example code:
      1
      2
      3
      4
      5
      6
      7
      
        <servers>
            <server>
                <id>TomcatServer</id>
                <username>root</username>
                <password>tomcat</password>
            </server>
        </servers>
      
    • Finally, add the tomcat maven configuration in pom.xml:
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      
        <plugin>
            <groupId>org.apache.tomcat.maven</groupId>
            <artifactId>tomcat7-maven-plugin</artifactId>
            <version>2.2</version>
            <configuration>
                <url>http://localhost:8080/manager/text</url>
                <server>TomcatServer</server>
                <path>/</path>
            </configuration>
        </plugin>
      
  5. After adding tomcat maven plugin, use command tomcat7:redeploy or tomcat7:deploy to automatically build war and quickly deploy the project. Remember to start tomcat first.
  6. Now to support jsp in jersey,first replace <servlet> in web.xml with <filter>.
  7. Also add following params in the web.xml filter tag:
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    
     <init-param>
         <param-name>jersey.config.server.provider.classnames</param-name>
         <param-value>org.glassfish.jersey.media.multipart.MultiPartFeature,org.glassfish.jersey.server.mvc.jsp.JspMvcFeature
         </param-value>
     </init-param>
     <init-param>
         <param-name>jersey.config.servlet.filter.forwardOn404</param-name>
         <param-value>true</param-value>
     </init-param>
     <init-param>
         <param-name>jersey.config.servlet.filter.staticContentRegex</param-name>
         <param-value>/(resources|(webapp))/.*</param-value>
     </init-param>
    
  8. Above init config will support JSPs under webapp folder.
  9. Now in order to pass data between Jersey endpoint to JSPs, we use ‘Viewable’ class. The data passed using viewable can be accessed as the variable ‘model’ using JSTL. Example code for viewable:
    1
    2
    3
    4
    5
    6
    
     @GET
     @Path("/")
     @Produces(MediaType.APPLICATION_JSON)
     public Viewable getData(){
         return new Viewable("<path to jsp file>", object);
     }
    
  10. Add following additional dependencies according to use-case:
    • jackson databind and annotations for serialization/deserialization
    • commons-dbcp for db connection pool setup
    • hibernate for db orm
    • mysql-connector for mysl connection
    • postgresql for postgreSql support
    • jedis for redis support (+ commons-pool2 as dependency for jedis)
    • amqp-client for rabbitmq support
  11. Now First Jackson.
    • In Jackson, we can use ‘ObjectMapper’ and ‘TypeFactory’ to get any type of object/value from json.
    • ObjectMapper object has a function ‘readValue’ which can be used to get value from json. Its a overloaded function: One type takes two argument ‘json String’ and ‘class’ and Second type takes two argument ‘json String’ and ‘TypeFactory object’
    • TypeFactory class can be used to create type for complex java object. Example, it can used for creating type for Java Collections.
    • Both these function along with TypeFactory can be used to create any Java objects from Json
  12. Now DbCP
    • DbCP can be easily integrated.
    • Just create a ‘BasicDataSource’ object.
    • Set Db jdbc url (Don’t forget to add ssl and public key retrieval params), username, password, max/min idle connections, max open prepared statements etc
    • Now this object can be used to get connection using ‘getConnection()’ method
  13. Now Zookeeper
    • Zookeeper connection is created using he ‘ZooKeeper’ constructor which requires the zk host, the connection time and watcher.
    • A watcher is used to get notification if data in zookeeper is changed/updated.
    • Now using this object, we can create znode, set/get data from znode or delete the znode.
    • For creating the znode, we use ‘create’ method which takes path, data(in bytes), List of ACLs, and the mode of creation.
    • An Acl is used to define authorization for the znode. We can create ACL object using the constructor with same name. This constructor requires the type of permission granted along with ‘Id’ object. This Id object is created using constructor which requires the scheme of authentication and “username:(‘username:password’ in MD5 hash)” as second argument.
    • Now we can get/set data in this node using the zk object and methods “getData”/”setData”. Remember to add auth details to zk object using ‘addAuthInfo’ method which takes two argument: one the scheme and second (‘username:password’ in bytes). Also set requires version of the znode which we can get using ‘exists’ method which requires path and watch(true/false) as argument.
    • Lastly, we can delete the znode using ‘delete’ method which requires the path and version.
  14. Now Redis
    • To get a redis connection object, we first setup jedis pool object.
    • JedisPool object constructor requires 3 arguments : First the JedisPoolConfig object, second the host, and last the port.
    • JedisPoolConfig is used to set init config of the pool like max total connections, max idle connection, timeout etc.
    • From this JedisPool object, we can get Jedis object using ‘getResource()’ method. If the db is password protected, use ‘auth(…)’ method og Jedis object to pass the password to authenticate the connection and operations.
    • In redis cli, use ‘CONFIG SET requirepass ' to set password. This will set password for current session. To persist this config on restart, use 'CONFIG REWRITE' after setting password to rewrite the config permanently.
    • Now using this Jedis object, we can perform all redis operations (All the operation methods name is same as the redis cli commands)
    • After performing operations, use ‘close()’ method to close the connection.
  15. Now RabbitMQ
    • Install rabbitmq on the system.
    • Add user using following commands:
      1
      2
      3
      
         sudo rabbitmqctl add_user <user> <password>
         sudo rabbitmqctl set_user_tags <user> administrator
         sudo rabbitmqctl set_permissions -p / <user> '.*' '.*' '.*' 
      
    • Additionally, web interface can be initialized using command:
      1
      
         sudo rabbitmq-plugins enable rabbitmq_management
      
    • In Java, first create a ‘ConnectionFactory’ object.
    • Using this object get the ‘Connection’ object using ‘newConnection()’ method
    • Now using this object get ‘Channel’ object using ‘createChannel()’ method
    • Now this channel object can be used to publish/consume data
    • Before publishing/consuming, we need to set the queue for the messaging process. To set queue, use ‘quereDeclare()’ method like:
         channel.queueDeclare(QUEUE_NAME, false, false, false, null);
      

      Just set the queue name and leave rest as above. This method does not create new queue if already exist.

    • Now for publishing, use ‘basicPublish()’ method like:
         channel.basicPublish("" , QUEUE_NAME, null, message.getBytes());
      

      Just set the queue name and message(in bytes) and leave rest as above. This will publish the message to the given queue. This is visible on web interface.

    • Now for consuming, use ‘basicConsume()’ method like:
         channel.basicConsume(QUEUE_NAME, true, DELIVERY_CALLBACK, consumerTag ->{});
      

      Here we pass the queue name and ‘DeliveryCallback’ object. DeliveryCallback object is created to handle the upcoming messages. Example code:

         DeliverCallback deliverCallback = (consumerTag, delivery) -> {
             String message = new String(delivery.getBody(), "UTF-8");
             System.out.println(" [x] Received '" + message + "'");
         };
      
    • Note, for publishing you can close the channel and connection after publishing but while consuming if both are closed then the code will not receive future messages. So while consuming don’t close them explicitly.
  16. Now Kafka
    • Not going too deep so Kafka is a messaging service like RabbitMQ but built for enterprise level applications. It provides better scalability. One extra feature is that it can store data received in queue for a pre-defined time.
  17. Now NGINX
    • It is a web server which provides various facilities like serving static files, reverse proxy, load balancing, mail server etc
    • Refer for more detail : https://www.javatpoint.com/nginx-tutorial
  18. To Do:
    • Jenkins
    • Docker
    • Kubernetes
    • Cloud