Author Archives: yogesh.mali@gmail.com

Redis Caching with RedisCacheManager

Introduction

In the previous post Redis Caching, we saw how to use Redis caching with all default settings. We didn’t have any Cache Manager or anything, but we were able to cache data. In this post, we will show how to use RedisCacheManager to cache the data. This manager can further be extended to customize the caching configuration even more. But we will not be looking into customization in this post particularly.

RedisCacheManager with Redis

Implement CacheManager for RedisCacheManager

Most of the code for this post will be similar to what we implemented in the previous post. We will just show how to use CacheManager.

To implement CacheManager first we remove @EnableCaching annotation from the main class SpringAppCacheApplication. Now we add a new CacheConfig class to configure our cache manager.

Basically, this CacheConfig will define CacheManager which build a redisTemplate to get JedisConnectionFactory which will be our java client to connect to our Redis server. This JedisConnectionFactory will get server host and port properties from application.properties file. The source code will look like below:

package com.betterjavacode.config;


import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.cache.CacheManager;
import org.springframework.cache.annotation.CachingConfigurerSupport;
import org.springframework.cache.annotation.EnableCaching;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.PropertySource;
import org.springframework.context.support.PropertySourcesPlaceholderConfigurer;
import org.springframework.data.redis.cache.RedisCacheManager;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.connection.jedis.JedisConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;

@Configuration
@EnableCaching
@ComponentScan("com.betterjavacode.config")
@PropertySource("classpath:/application.properties")
public class CacheConfig extends CachingConfigurerSupport
{
    private static final Logger LOGGER = LoggerFactory.getLogger(CacheConfig.class);
    private @Value("${spring.redis.host}") String redisHost;
    private @Value("${spring.redis.port}") int redisPort;

    @Bean
    public static PropertySourcesPlaceholderConfigurer propertySourcesPlaceholderConfigurer() {
        return new PropertySourcesPlaceholderConfigurer();
    }

    @Bean
    public JedisConnectionFactory redisConnectionFactory()
    {
        LOGGER.info(" Inside redisConnectionFactory()...");

        JedisConnectionFactory redisConnectionFactory = new JedisConnectionFactory();

        redisConnectionFactory.setHostName(redisHost);
        redisConnectionFactory.setPort(redisPort);
        redisConnectionFactory.setUsePool(true);
        return redisConnectionFactory;
    }

    @Bean
    public RedisTemplate<String, String> redisTemplate(RedisConnectionFactory rf)
    {
        LOGGER.info(" Inside redisTemplate()...");

        RedisTemplate<String, String> redisTemplate = new RedisTemplate<>();
        redisTemplate.setConnectionFactory(redisConnectionFactory());
        return redisTemplate;
    }

    @Bean
    public CacheManager cacheManager(RedisTemplate redisTemplate)
    {
        LOGGER.info(" Inside cacheManager()...");
        RedisCacheManager cacheManager = new RedisCacheManager(redisTemplate);
        cacheManager.setDefaultExpiration(300);
        return cacheManager;
    }
}

Now if we build our application and run it, Spring boot console will show the following output

2018-02-28 20:31:41.913  INFO 9856 --- [ost-startStop-1] o.s.b.w.servlet.FilterRegistrationBean   : Mapping filter: 'requestContextFilter' to: [/*]
2018-02-28 20:31:42.034  INFO 9856 --- [           main] o.s.j.d.DriverManagerDataSource          : Loaded JDBC driver:com.mysql.jdbc.Driver
2018-02-28 20:31:42.244  INFO 9856 --- [           main] j.LocalContainerEntityManagerFactoryBean : Building JPA container EntityManagerFactory for persistence unit 'default'
2018-02-28 20:31:42.288  INFO 9856 --- [           main] o.hibernate.jpa.internal.util.LogHelper  : HHH000204: Processing PersistenceUnitInfo [ name: default        ...]
2018-02-28 20:31:42.495  INFO 9856 --- [           main] org.hibernate.Version                    : HHH000412: Hibernate
 Core {5.2.13.Final}
2018-02-28 20:31:42.499  INFO 9856 --- [           main] org.hibernate.cfg.Environment            : HHH000206: hibernate
.properties not found
2018-02-28 20:31:42.599  INFO 9856 --- [           main] o.hibernate.annotations.common.Version   : HCANN000001: Hiberna
te Commons Annotations {5.0.1.Final}
2018-02-28 20:31:43.688  INFO 9856 --- [           main] org.hibernate.dialect.Dialect            : HHH000400: Using dia
lect: org.hibernate.dialect.MySQL5Dialect
2018-02-28 20:31:43.764  INFO 9856 --- [           main] o.h.e.j.e.i.LobCreatorBuilderImpl        : HHH000423: Disabling
 contextual LOB creation as JDBC driver reported JDBC version [3] less than 4
2018-02-28 20:31:44.684  INFO 9856 --- [           main] j.LocalContainerEntityManagerFactoryBean : Initialized JPA Enti
tyManagerFactory for persistence unit 'default'
2018-02-28 20:31:45.184  INFO 9856 --- [           main] com.betterjavacode.config.CacheConfig    :  Inside redisConnectionFactory()...
2018-02-28 20:31:45.288  INFO 9856 --- [           main] com.betterjavacode.config.CacheConfig    :  Inside redisTemplate()...
2018-02-28 20:31:45.346  INFO 9856 --- [           main] com.betterjavacode.config.CacheConfig    :  Inside cacheManager()...
2018-02-28 20:31:45.985  INFO 9856 --- [           main] s.w.s.m.m.a.RequestMappingHandlerAdapter : Looking for @ControllerAdvice: org.springframework.boot.context.embedded.AnnotationConfigEmbeddedWebApplicationContext@30946e09: startup dat
e [Wed Feb 28 20:31:37 CST 2018]; root of context hierarchy
2018-02-28 20:31:46.214  INFO 9856 --- [           main] s.w.s.m.m.a.RequestMappingHandlerMapping : Mapped "{[/cachedemo/v1/companies/{id}/],methods=[GET],produces=[application/json]}" onto public com.betterjavacode.models.Company com.bette
rjavacode.resources.CompanyController.getCompany(int)
2018-02-28 20:31:46.217  INFO 9856 --- [           main] s.w.s.m.m.a.RequestMappingHandlerMapping : Mapped "{[/cachedemo/v1/companies],methods=[GET],produces=[application/json]}" onto public java.util.List<com.betterjavacode.models.Company>
 com.betterjavacode.resources.CompanyController.getAllCompanies()
2018-02-28 20:31:46.222  INFO 9856 --- [           main] s.w.s.m.m.a.RequestMappingHandlerMapping : Mapped "{[/error]}"onto public org.springframework.http.ResponseEntity<java.util.Map<java.lang.String, java.lang.Object>> org.springframewo
rk.boot.autoconfigure.web.BasicErrorController.error(javax.servlet.http.HttpServletRequest)
2018-02-28 20:31:46.223  INFO 9856 --- [           main] s.w.s.m.m.a.RequestMappingHandlerMapping : Mapped "{[/error],produces=[text/html]}" onto public org.springframework.web.servlet.ModelAndView org.springframework.boot.autoconfigure.web
.BasicErrorController.errorHtml(javax.servlet.http.HttpServletRequest,javax.servlet.http.HttpServletResponse)
2018-02-28 20:31:46.300  INFO 9856 --- [           main] o.s.w.s.handler.SimpleUrlHandlerMapping  : Mapped URL path [/webjars/**] onto handler of type [class org.springframework.web.servlet.resource.ResourceHttpRequestHandler]
2018-02-28 20:31:46.301  INFO 9856 --- [           main] o.s.w.s.handler.SimpleUrlHandlerMapping  : Mapped URL path [/**] onto handler of type [class org.springframework.web.servlet.resource.ResourceHttpRequestHandler]
2018-02-28 20:31:46.377  INFO 9856 --- [           main] o.s.w.s.handler.SimpleUrlHandlerMapping  : Mapped URL path [/**/favicon.ico] onto handler of type [class org.springframework.web.servlet.resource.ResourceHttpRequestHandler]
2018-02-28 20:31:47.071  INFO 9856 --- [           main] o.s.j.e.a.AnnotationMBeanExporter        : Registering beans for JMX exposure on startup
2018-02-28 20:31:47.184  INFO 9856 --- [           main] s.b.c.e.t.TomcatEmbeddedServletContainer : Tomcat started on port(s): 8080 (http)
2018-02-28 20:31:47.195  INFO 9856 --- [           main] c.b.S.SpringAppCacheApplication          : Started SpringAppCacheApplication in 10.626 seconds (JVM running for 11.552)

In this console output, we will see our log statements Inside redisConnectionFactory, Inside redisTemplate, Inside cacheManager.

Conclusion

In this short post, we showed how to use RedisCacheManager to configure Redis for a spring boot application.

References

 

Caching: How to use Redis Caching with Spring Boot

In this introductory post, we will show how to use Redis caching in a simple spring boot application. In subsequent posts, we will evaluate different factors of Redis caching. But for now, we will try to focus on the simple problem of providing caching to a rest service that provides companies-related data to the user interface. This data is in a database, but caching will help us improve the performance.

 

What you need

  • Java 8
  • MySQL Database
  • IntelliJ Editor
  • Gradle
  • Redis Server and Redis Desktop Manager

Spring Boot Based Rest Service

As part of this post, we will build a simple spring-boot based rest service. This rest service will provide data related to companies which will be stored in mysql database.

We will be using Gradle to build our dependencies in this project. Important dependencies for this project are spring-boot-starter, spring-boot-jpa and spring-boot-starter-data-redis With all the needed Gradle dependencies, our Gradle script will look like below:

buildscript {
  ext {
    springBootVersion = '1.5.10.RELEASE'
  }
  repositories {
    mavenCentral()
  }
  dependencies {
    classpath("org.springframework.boot:spring-boot-gradle-plugin:${springBootVersion}")
  }
}

apply plugin: 'java'
apply plugin: 'eclipse'
apply plugin: 'org.springframework.boot'

group = 'com.betterjavacode'
version = '0.0.1-SNAPSHOT'
sourceCompatibility = 1.8

repositories {
  mavenCentral()
}

jar {
    manifest {
        attributes 'Main-Class':'com.betterjavacode.SpringAppCache.SpringAppCacheApplication'
    }
    baseName= 'SpringAppCache'
    version='0.0.1-SNAPSHOT'
}

dependencies {
  compile('org.springframework.boot:spring-boot-starter')
  compile('org.springframework.data:spring-data-jpa')
  compile('org.springframework.boot:spring-boot-starter-data-redis')
  compile('org.springframework.boot:spring-boot-starter-web')
        compile('org.hibernate.javax.persistence:hibernate-jpa-2.1-api:1.0.0.Final')
        compile('mysql:mysql-connector-java:5.1.6')
        compile('org.hibernate:hibernate-core:5.2.13.Final')   
        compile('org.aspectj:aspectjweaver:1.8.13')
  testCompile('org.springframework.boot:spring-boot-starter-test')
}

Let’s build a model class for the object Company which will look like below:

package com.betterjavacode.models;

import javax.persistence.*;
import java.io.Serializable;

@Entity(name="Company")
@Table(name="company")
public class Company implements Serializable
{
    public Company()
    {

    }

    @Id
    @GeneratedValue(strategy = GenerationType.AUTO)
    private int id;
    @Column(nullable=false)
    private String name;
    @Column(nullable=false)
    private String type;

    public Company(int id, String name, String type)
    {
        this.id = id;
        this.type = type;
        this.name = name;
    }

    public int getId()
    {
        return id;
    }

    public void setId(int id)
    {
        this.id = id;
    }

    public String getName()
    {
        return name;
    }

    public void setName(String name)
    {
        this.name = name;
    }

    public String getType()
    {
        return type;
    }

    public void setType(String type)
    {
        this.type = type;
    }
}

We will not be showing any of the middle layer code which is mostly how the data is going to be built.

Our RestController will use an autowired CompanyManager to fetch company data from the database.

Before we build RestController, we will show the configuration that we have annotated in SpringAppCacheApplication main class.

package com.betterjavacode.SpringAppCache;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cache.annotation.EnableCaching;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.data.jpa.repository.config.EnableJpaRepositories;

@SpringBootApplication
@EnableAutoConfiguration
@ComponentScan(basePackages = "com.betterjavacode")
@EnableJpaRepositories(basePackages = "com.betterjavacode.repositories")
@EnableCaching
public class SpringAppCacheApplication
{
  public static void main(String[] args) {
    SpringApplication.run(SpringAppCacheApplication.class, args);
  }
}

Here you can see, we have enabled caching with annotation @EnableCaching.

Now in our RestController class CompanyController , this will show annotation of @Cachable that helps decide when to cache data for the incoming request. This annotation caches data that has been fetched for the request based on configuration.

package com.betterjavacode.resources;

import java.util.List;

import com.betterjavacode.interfaces.CompanyManager;
import com.betterjavacode.models.Company;
import org.hibernate.annotations.Cache;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.http.HttpStatus;
import org.springframework.web.bind.annotation.*;


import javax.websocket.server.PathParam;

@RestController
@RequestMapping(value="/cachedemo/v1")
public class CompanyController
{


    @Autowired
    public CompanyManager companyManager;


    @RequestMapping(value = "/companies", method= RequestMethod.GET,
    produces = {"application/json"})
    @ResponseStatus(HttpStatus.OK)
    @ResponseBody
    @Cacheable("companies")
    public List<Company> getAllCompanies()
    {
        return companyManager.getAllCompanies();
    }


    @RequestMapping(value = "/companies/{id}/", method = RequestMethod.GET, produces = {"application/json"})
    @ResponseStatus(HttpStatus.OK)
    @ResponseBody
    @Cacheable(value = "company", key = "#id")
    public Company getCompany(@PathVariable("id") int id)
    {
        return companyManager.getCompany(id);
    }
}

Here is a controller, if you see we are caching the data coming from the database with annotation @Cacheable

To make sure data gets cached with Redis server, we will need certain properties where these annotations will help us to cache the data. The properties to configure Redis server are below:

#########################################################################################
## REDIS CACHE
#########################################################################################
spring.cache.type = redis
spring.redis.host = 127.0.0.1
spring.redis.port = 6379

Once you build the project and run it, we will be able to perform the REST requests to fetch data. If we perform the same requests multiple times, we will be able to see the data in redis.

Conclusion

In this post, we showed how to use redis-caching to cache the data for a spring boot based REST service. The code from this post is available to download github

 

One Tip to Avoid Bouncy Castle Error

java.lang.NoSuchMethodError: org.bouncycastle.math.ec.ECCurve$Fp – Issue

In this post, I will discuss one tip everyone can use to avoid bouncy castle error.

Problem

Recently I was working on building a SOAP webservice where we were using Apache CXF libraries along with Spring boot. We built the webservice, but when we were sending a SOAP request through the client, we kept getting following error:

Caused by: java.lang.NoSuchMethodError: org.bouncycastle.math.ec.ECCurve$Fp.<init>(Ljava/math/BigInteger;Ljava/math/BigInteger;Ljava/math/BigInteger;)V
  at org.bouncycastle.jcajce.provider.asymmetric.util.EC5Util.convertCurve(Unknown Source)
  at org.bouncycastle.jcajce.provider.asymmetric.util.EC5Util.convertPoint(Unknown Source)
  at org.bouncycastle.jcajce.provider.asymmetric.ec.BCECPublicKey.<init>(Unknown Source)
  at org.bouncycastle.jcajce.provider.asymmetric.ec.KeyFactorySpi.engineGeneratePublic(Unknown Source)
  at java.security.KeyFactory.generatePublic(KeyFactory.java:334)
  at sun.security.ssl.HandshakeMessage$ECDH_ServerKeyExchange.<init>(HandshakeMessage.java:1075)
  at sun.security.ssl.ClientHandshaker.processMessage(ClientHandshaker.java:284)
  at sun.security.ssl.Handshaker.processLoop(Handshaker.java:1026)
  at sun.security.ssl.Handshaker.process_record(Handshaker.java:961)
  at sun.security.ssl.SSLSocketImpl.readRecord(SSLSocketImpl.java:1062)
  at sun.security.ssl.SSLSocketImpl.performInitialHandshake(SSLSocketImpl.java:1375)
  at sun.security.ssl.SSLSocketImpl.startHandshake(SSLSocketImpl.java:1403)
  at sun.security.ssl.SSLSocketImpl.startHandshake(SSLSocketImpl.java:1387)
  at org.apache.http.conn.ssl.SSLSocketFactory.connectSocket(SSLSocketFactory.java:553)
  at org.apache.http.conn.ssl.SSLSocketFactory.connectSocket(SSLSocketFactory.java:412)
  at org.apache.http.impl.conn.DefaultClientConnectionOperator.openConnection(DefaultClientConnectionOperator.java:179)
  at org.apache.http.impl.conn.ManagedClientConnectionImpl.open(ManagedClientConnectionImpl.java:328)
  at org.apache.http.impl.client.DefaultRequestDirector.tryConnect(DefaultRequestDirector.java:612)
  at org.apache.http.impl.client.DefaultRequestDirector.execute(DefaultRequestDirector.java:447)
  at org.apache.http.impl.client.AbstractHttpClient.doExecute(AbstractHttpClient.java:884)
  at org.apache.http.impl.client.CloseableHttpClient.execute(CloseableHttpClient.java:82)
  at org.apache.http.impl.client.CloseableHttpClient.execute(CloseableHttpClient.java:107)
  at org.apache.http.impl.client.CloseableHttpClient.execute(CloseableHttpClient.java:55)
  at org.springframework.ws.transport.http.HttpComponentsConnection.onSendAfterWrite(HttpComponentsConnection.java:121)

A Simple tip to avoid this error

We tried different things to resolve this issue. We tried to exclude bouncycastle jars from cxf-rt-ws-security dependencies we were using as we were pulling them from other dependencies. But this error would still pop up intermittently. Eventually, I figured out the issue. We had to add this dependency of bouncycastle explicitly as below in our Gradle build file. That’s when the error went away.

compile (‘org.bouncycastle:bcprov-jdk15on:1.56’) 

Conclusion

In this post, I showed how to use this one tip to avoid the error caused by the bouncy castle. If you enjoyed this post, subscribe to my blog here.

Database design and discussion – Part II

In the previous post database design, we discussed what our database will look like. But that was just half of the database design discussion as we still have to cover the heart and soul of our SocialPie service. In this post, we will cover the other half and that is the APIs we will use from Twitter, Instagram, and Facebook.

Using Instagram APIs

So Instagram which is now part of Facebook offers a marketing API for businesses. You can find more detail on the Instagram API. This API is built on Facebook’s Graph API. An interesting thing to look at this API, we will find what kind of data we are actually looking to get and store in our database.

This API offers something called Insights API, it provides us the data for user metrics for business accounts and stories metrics. Considering Instagram API is linked with Facebook, we will be using the same API for Facebook data.

/media/insights/ –  This API gives us details about engagements, impressions, and reach about stories. A sample response looks like below:

{
  "data": [
    {
      "name": "impressions",
      "period": "lifetime",
      "values": [
        {
          "value": 264
        }
      ],
      "title": "Impressions",
      "description": "Total number of times the media object has been seen",
      "id": "17855590849148465/insights/impressions/lifetime"
    },
    {
      "name": "reach",
      "period": "lifetime",
      "values": [
        {
          "value": 103
        }
      ],
      "title": "Reach",
      "description": "Total number of unique accounts that have seen the media object",
      "id": "17855590849148465/insights/reach/lifetime"
    }
  ]
}

/user/insights/ – This API gives us different metrics data for business accounts. These metrics include impressions, follower counts, website clicks, text message clicks, profile views, online followers. A sample response looks like below:

{
  "data": [
    {
      "name": "impressions",
      "period": "day",
      "values": [
        {
          "value": 4,
          "end_time": "2017-05-04T07:00:00+0000"
        },
        {
          "value": 66,
          "end_time": "2017-05-05T07:00:00+0000"
        }
      ],
      "title": "Impressions",
      "description": "Total number of times this profile has been seen",
      "id": "17841400008460056/insights/impressions/day"
    },
    {
      "name": "reach",
      "period": "day",
      "values": [
        {
          "value": 3,
          "end_time": "2017-05-04T07:00:00+0000"
        },
        {
          "value": 36,
          "end_time": "2017-05-05T07:00:00+0000"
        }
      ],
      "title": "Reach",
      "description": "Total number of unique accounts that have seen this profile",
      "id": "17841400008460056/insights/reach/day"
    },
    {
      "name": "profile_views",
      "period": "day",
      "values": [
        {
          "value": 0,
          "end_time": "2017-05-04T07:00:00+0000"
        },
        {
          "value": 2,
          "end_time": "2017-05-05T07:00:00+0000"
        }
      ],
      "title": "Profile Views",
      "description": "Total number of unique accounts that have viewed this profile within the specified period",
      "id": "17841400008460056/insights/profile_views/day"
    }
  ]
}

What fields we will use and build our database?

So what data from this API we will be using to build our database. We will have a table called InstagramData This table will include the following fields

  • impressions
  • reach
  • profile_views
  • followers
  • audience_gender_age
  • email_contacts
  • video_views

Therefore, we showed how we will be using Facebook and Instagram APIs. In the next post, we will look into Twitter API. Currently, Twitter does offer an enterprise API at a premium price. But if there is no open-source API for developers, we will not be using it in this project.

References

  1. Instagram API documentation – Instagram API

 

500 Miles

This is a non-programming blog post. I just wanted to announce the publication of my first fiction book 500 Miles.

500 Miles

The book contains 14 short stories about characters from train traveling. I wrote more about the book on my other blog 500 Miles at yogsma.

You can buy this book on Amazon India, Flipkart or Pothi.com. The links for the same are as below:

500 Miles at Amazon India

500 Miles at Flipkart

500 Miles at Pothi.com