10 February 2018

Pure Restful web services with Spring Data Rest

Last couple of weeks I had a chance to work with spring data rest project. When I read the documentation I thought I pretty awesome.Because It has lot of features for REST api development.Here I will describe main features contains in spring data rest. At the end of this thread I will create simple rest api for product management.

What is Pure Restful Webservice ?

First we talk about what is restful web service.  Restful web service is architectural style for request and response mapping in web service. There are different kind of HTTP request coming to API. But when it comes to REST api every url have different meaning for different HTTP request methiods.

Ex: /api/v1/student/  --- > GET
      /api/v1/student/ ----> POST
     /api/v1/student/ ---> PUT

And there are different response Status codes for different purposes.
Method Method
200 OK 201 Created
202 Accepted 203 Non-Authoritative Info
204 No content 205 Reset content
206 Partial content
300 Multiple choice 301 Moved permanently
302 Found 303 See other
304 Not modified 306 (unused)
307 Temporary redirect
400 Bad request 401 Unauthorized
402 Payment required 403 Forbidden
404 Not found 405 Method not allowed
406 Not acceptable 407 Proxy auth required
408 Timeout 409 Conflict
410 Gone 411 Length required
412 Preconditions failed 413 Request entity too large
414 Requested URI too long 415 Unsupported media
416 Bad request range 417 Expectation failed
500 Server error 501 Not implemented
502 Bad gateway 503 Service unavailable
504 Gateway timeout 505 Bad HTTP version

As Example When we POST student obejct to rest API it will need to Send 204 no content status code rather than 200 status code.likewise there are standard ways to send response.If all the standard are fulfill  it will become pure restful web service.

These are the core areas we are discus in this tutorial.
  1. Projection
  2. Search
  3. Advance Search
  4. Pagination & Sorting
  5. Spring Security
To Discuss spring data rest we used simple sales management demo. These are the entity classes. We have Product,Sale,SalesAgent

package dev.firelimez.io.domain;


import org.springframework.data.annotation.Id;


public class Product {
    @Id
    private String productId;
    private String name;
    private String price;

    public String getProductId() {
        return productId;
    }

    public void setProductId(String productId) {
        this.productId = productId;
    }

    public String getName() {
        return name;
    }

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

    public String getPrice() {
        return price;
    }

    public void setPrice(String price) {
        this.price = price;
    }
}



package dev.firelimez.io.domain;

import org.springframework.data.annotation.Id;
import org.springframework.data.mongodb.core.mapping.DBRef;

import java.util.List;

public class Sale {
    @Id
    private String salesId;
    private String date;

    @DBRef
    private SalesAgent salesAgent;

    @DBRef
    private List<Product> products;

    public String getSalesId() {
        return salesId;
    }

    public void setSalesId(String salesId) {
        this.salesId = salesId;
    }

    public String getDate() {
        return date;
    }

    public void setDate(String date) {
        this.date = date;
    }

    public List<Product> getProducts() {
        return products;
    }

    public void setProducts(List<Product> products) {
        this.products = products;
    }

    public SalesAgent getSalesAgent() {
        return salesAgent;
    }

    public void setSalesAgent(SalesAgent salesAgent) {
        this.salesAgent = salesAgent;
    }
}

package dev.firelimez.io.domain;


import org.springframework.data.annotation.Id;

import java.io.Serializable;

public class SalesAgent implements Serializable {

    @Id
    private String agentId;
    private String name;
    private String lastName;
    private String age;

    public String getName() {
        return name;
    }

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

    public String getLastName() {
        return lastName;
    }

    public void setLastName(String lastName) {
        this.lastName = lastName;
    }

    public String getAge() {
        return age;
    }

    public void setAge(String age) {
        this.age = age;
    }

    public String getAgentId() {
        return agentId;
    }

    public void setAgentId(String agentId) {
        this.agentId = agentId;
    }
}

So this tutorial I will use mongo database as database reference. First you have to add these dependencies to your maven project.


    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>1.5.9.RELEASE</version>
    </parent>

    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-rest</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-mongodb</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-security</artifactId>
        </dependency>
        <dependency>
            <groupId>com.google.code.gson</groupId>
            <artifactId>gson</artifactId>
            <version>2.8.2</version>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>

Spring data rest all are written using interfaces. So first thing we have to do is create repository. All the entity classes should have repository classes. As example this is the example Repository class.


package dev.firelimez.io.repo;

import dev.firelimez.io.domain.Product;
import org.springframework.data.mongodb.repository.MongoRepository;
import org.springframework.data.repository.query.Param;
import org.springframework.data.rest.core.annotation.RepositoryRestResource;
import org.springframework.data.rest.core.annotation.RestResource;

import java.util.List;

@RepositoryRestResource(collectionResourceRel = "products", path = "products")
public interface ProductRepository extends MongoRepository<Product, String> {

    @RestResource(path = "names", rel = "demo1")
    List<Product> findByName(@Param("name") String name);


    @RestResource(path = "similar", rel = "demo")
    List<Product> findByNameLike(@Param("name") String name);

    @Override
    @RestResource(exported = true)
    void delete(String productId);
}

And Final step is start program as spring boot application.

package dev.firelimez.io;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
public class StarterApplication {

    public static void main(String[] args) {
        SpringApplication.run(StarterApplication.class, args);
    }
}

And next step is add application.yml and configuration should be like this.

server:
    port: 9008

spring:
 data:
    mongodb:
      host: 127.0.0.1
      port: 27017
      database: sales_management

And that is your REST API  is ready.


Projection

Projection is concept expose specific fields to rest api end points. As example in sales rest API  how to get the sales agent details who are done the  sales. In this case we can use projection. For that we have to add what are the properties we expect when we using projection. 

Ex:  sales expand projection return sales agent details with the sales objects.


package dev.firelimez.io.domain.projection;

import dev.firelimez.io.domain.Sale;
import org.springframework.data.rest.core.config.Projection;

import java.util.List;

@Projection(name = "expand", types = Sale.class)
interface SalesProjection {

    String getSalesId();

    String getDate();

    SalesAgentProjection getSalesAgent();

    List<ProductProjection> getProducts();
}


package dev.firelimez.io.domain.projection;

import org.springframework.beans.factory.annotation.Value;

interface SalesAgentProjection {

    int getAge();

    String getAgentId();

    @Value("#{target.name} #{target.lastName}")
    String getFullName();
}


And then you can access projection data using this way.


http://localhost:9008/sales?projection=expand


Search & Advance Search

When we creating rest api biggest issue is dealing with database and based on user REST end point what we did was write query and get result from database and return to user. But spring data rest support queryless way to get result from database. 

Ex:  if we want to get product by product name 


@RestResource(path = "names", rel = "name")
    List<Product> findByName(@Param("name") String name);

These are the inbuild query methods in spring jpa repository.

Advanced Search

For advance search we have to use ExampleMatcher class and it will support advance search in spring data rest.


    @Autowired
    SalesAgentRepository salesAgentRepository;

    @RequestMapping(method = RequestMethod.POST, path = "/api/v2/agent/search/advance", consumes = MediaType.APPLICATION_JSON_VALUE)
    @ResponseBody
    public List<SalesAgent> claimSupportTicket(@RequestBody SalesAgent salesAgent) {
        ExampleMatcher exampleMatcher = ExampleMatcher.matching().withIgnoreNullValues().withIgnoreCase();
        List<SalesAgent> advanceSearch = salesAgentRepository.findAll(Example.of(salesAgent, exampleMatcher));
        return advanceSearch;
    }

20 October 2017

Spring Boot Exception Handling





Long time I couldn't have time to write in my blog. So this time I am going to talk about spring boot exception handling with error code mapping. This blog post helpful to who are new to spring boot. The first thing is define error codes.


 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
public enum ApiError {
    
    INVALID_REQUEST_PARAMETERS("E10001", "Invalid request parameters"),

    PRODUCT_NOT_FOUND("E1001", "Product not found");

    private final String errorCode;
    private final String errorMessage;

    ApiError(String errorCode, String errorMessage) {
        this.errorCode = errorCode;
        this.errorMessage = errorMessage;
    }

    public String getErrorCode() {
        return errorCode;
    }

    public String getErrorMessage() {
        return errorMessage;
    }
}


The next task is write  runtime exception type based on HTTP status.


 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
import io.apptizer.api.errors.codes.ApiError;

public class ResourceNotFoundException extends RuntimeException {

    private ApiError apiErrors;

    public ResourceNotFoundException(ApiError apiErrors, String message) {
        super(message);
        this.apiErrors = apiErrors;
    }

    public ApiErrors getApiErrors() {
        return apiErrors;
    }

    public void setApiErrors(ApiErrors apiErrors) {
        this.apiErrors = apiErrors;
    }
}

And final task is write exception mapper handler.


 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;

@ControllerAdvice
public class ExceptionMapperHandler {
    @ExceptionHandler(ResourceNotFoundException.class)
    public ResponseEntity<ErrorInfo> resourceNotFound(ResourceNotFoundException ex) {
        ErrorInfo errorInfo = new ErrorInfo(ex.getApiErrors().getErrorCode(), ex.getMessage());
        return new ResponseEntity<>(errorInfo, HttpStatus.NOT_FOUND);
    }
}

then in your rest service you can throw exception with error code mapping.

throw new ResourceNotFoundException(ApiError.PRODUCT_NOT_FOUND, "Product Task Not Found");

And that is it.If you have any better way please put your comments.

12 August 2017

Android ADB Screenshot Automation



In this blog post  I will describe how to take screenshot from emulator or device in android. This commands only works for single adb device.I have write some shell script to take screenshots. Anyway android studio have feature to take screenshot .But if your not going with studio this might help.Hope this will help resolve your problems.

#!/bin/bash
adb shell am start -n juwelary.innova.dev.juwelarytemplate/juwelary.innova.dev.juwelarytemplate.MainActivity
sleep 5
name=screenshot.png
echo "Start to take screenshot"
adb shell screencap -p /sdcard/$name
adb pull /sdcard/$name
adb shell rm /sdcard/$name
curr_dir=pwd
echo "save to `pwd`/$name"

09 April 2017

Android Client Benchmark AsyncTask & Retrofit & OkHttp

Hi guys , After long time. Last couple of months I worked in android development so I thought share something i have discovered . This is a open thread so if you have any other opinions you can post here. What I am going to do is comparison between android client libraries. Here  I am going to do some benchmark on

  1. Retrofit  
  2. OkHttp 
  3. HttpURLConnection 


The main thing is developers wonder which library is fit for their android application. Here I will post the code examples and results. And one more thing here I am not taking volley because it is old library now.If you need to access code this is the url [https://github.com/sajith4u/android-client-benchmark ]. Feel free to commit your changes.

Code Samples

Retrofit

public interface RetrofitClient {

    @GET("benchmark/")
    Call<Example> getBenchmarkResults();
}


  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
package app.innova.dev.cleintbenchmark.beans;


import com.google.gson.annotations.Expose;
import com.google.gson.annotations.SerializedName;

public class Example {

    @SerializedName("text")
    @Expose
    private String text;
    @SerializedName("to_user_id")
    @Expose
    private int toUserId;
    @SerializedName("to_user")
    @Expose
    private String toUser;
    @SerializedName("from_user")
    @Expose
    private String fromUser;
    @SerializedName("result_type")
    @Expose
    private String resultType;
    @SerializedName("recent_retweets")
    @Expose
    private int recentRetweets;
    @SerializedName("id")
    @Expose
    private int id;
    @SerializedName("from_user_id")
    @Expose
    private int fromUserId;
    @SerializedName("iso_language_code")
    @Expose
    private String isoLanguageCode;
    @SerializedName("source")
    @Expose
    private String source;
    @SerializedName("profile_image_url")
    @Expose
    private String profileImageUrl;
    @SerializedName("created_at")
    @Expose
    private String createdAt;
    @SerializedName("since_id")
    @Expose
    private int sinceId;
    @SerializedName("max_id")
    @Expose
    private int maxId;
    @SerializedName("refresh_url")
    @Expose
    private String refreshUrl;
    @SerializedName("results_per_page")
    @Expose
    private int resultsPerPage;
    @SerializedName("next_page")
    @Expose
    private String nextPage;
    @SerializedName("completed_in")
    @Expose
    private float completedIn;
    @SerializedName("page")
    @Expose
    private int page;
    @SerializedName("query")
    @Expose
    private String query;

    public String getText() {
        return text;
    }

    public void setText(String text) {
        this.text = text;
    }

    public int getToUserId() {
        return toUserId;
    }

    public void setToUserId(int toUserId) {
        this.toUserId = toUserId;
    }

    public String getToUser() {
        return toUser;
    }

    public void setToUser(String toUser) {
        this.toUser = toUser;
    }

    public String getFromUser() {
        return fromUser;
    }

    public void setFromUser(String fromUser) {
        this.fromUser = fromUser;
    }

    public String getResultType() {
        return resultType;
    }

    public void setResultType(String resultType) {
        this.resultType = resultType;
    }

    public int getRecentRetweets() {
        return recentRetweets;
    }

    public void setRecentRetweets(int recentRetweets) {
        this.recentRetweets = recentRetweets;
    }

    public int getId() {
        return id;
    }

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

    public int getFromUserId() {
        return fromUserId;
    }

    public void setFromUserId(int fromUserId) {
        this.fromUserId = fromUserId;
    }

    public String getIsoLanguageCode() {
        return isoLanguageCode;
    }

    public void setIsoLanguageCode(String isoLanguageCode) {
        this.isoLanguageCode = isoLanguageCode;
    }

    public String getSource() {
        return source;
    }

    public void setSource(String source) {
        this.source = source;
    }

    public String getProfileImageUrl() {
        return profileImageUrl;
    }

    public void setProfileImageUrl(String profileImageUrl) {
        this.profileImageUrl = profileImageUrl;
    }

    public String getCreatedAt() {
        return createdAt;
    }

    public void setCreatedAt(String createdAt) {
        this.createdAt = createdAt;
    }

    public int getSinceId() {
        return sinceId;
    }

    public void setSinceId(int sinceId) {
        this.sinceId = sinceId;
    }

    public int getMaxId() {
        return maxId;
    }

    public void setMaxId(int maxId) {
        this.maxId = maxId;
    }

    public String getRefreshUrl() {
        return refreshUrl;
    }

    public void setRefreshUrl(String refreshUrl) {
        this.refreshUrl = refreshUrl;
    }

    public int getResultsPerPage() {
        return resultsPerPage;
    }

    public void setResultsPerPage(int resultsPerPage) {
        this.resultsPerPage = resultsPerPage;
    }

    public String getNextPage() {
        return nextPage;
    }

    public void setNextPage(String nextPage) {
        this.nextPage = nextPage;
    }

    public float getCompletedIn() {
        return completedIn;
    }

    public void setCompletedIn(float completedIn) {
        this.completedIn = completedIn;
    }

    public int getPage() {
        return page;
    }

    public void setPage(int page) {
        this.page = page;
    }

    public String getQuery() {
        return query;
    }

    public void setQuery(String query) {
        this.query = query;
    }

    @Override
    public String toString() {
        return "Example{" +
                "text='" + text + '\'' +
                ", toUserId=" + toUserId +
                ", toUser='" + toUser + '\'' +
                ", fromUser='" + fromUser + '\'' +
                ", resultType='" + resultType + '\'' +
                ", recentRetweets=" + recentRetweets +
                ", id=" + id +
                ", fromUserId=" + fromUserId +
                ", isoLanguageCode='" + isoLanguageCode + '\'' +
                ", source='" + source + '\'' +
                ", profileImageUrl='" + profileImageUrl + '\'' +
                ", createdAt='" + createdAt + '\'' +
                ", sinceId=" + sinceId +
                ", maxId=" + maxId +
                ", refreshUrl='" + refreshUrl + '\'' +
                ", resultsPerPage=" + resultsPerPage +
                ", nextPage='" + nextPage + '\'' +
                ", completedIn=" + completedIn +
                ", page=" + page +
                ", query='" + query + '\'' +
                '}';
    }
}


 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
final RetrofitClient apiService =
                RestClient.getClient().create(RetrofitClient.class);
        retrofit.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                String format = sdf.format(new Date());
                Log.d(TAG, "Date : " + format);
                Call<Example> call = apiService.getBenchmarkResults();
                call.enqueue(new Callback<Example>() {
                    @Override
                    public void onResponse(Call<Example> call, Response<Example> response) {
                        Log.d(TAG, String.valueOf(response.code()));
                        Example movies = response.body();
                        if (movies != null) {
                            String format = sdf.format(new Date());
                            Log.d(TAG, "Date : " + format);
                            System.out.println("Data :" + movies.toString());
                            Log.d(TAG, "Data :" + movies.toString());
                        }

                    }

                    @Override
                    public void onFailure(Call<Example> call, Throwable t) {
                        Log.e(TAG, t.toString());
                    }
                });
            }
        });

Android AsyncTask



 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
public class AsyncClient {

    private String MOCK_URL = "http://demo7836701.mockable.io/benchmark";

    private static String GET = "GET";

    private static int CONNECTION_TIMEOUT = 75000;

    private static int READ_TIMEOUT = 60000;

    public Object getObject(Class successClass) throws Exception {
        Object getResponse;
        StringBuilder responseString = new StringBuilder();
        String fullUrl = (MOCK_URL).replace(" ", "%20");
        URL requestUrl = new URL(fullUrl);
        HttpURLConnection conn = getConnection(GET, requestUrl);
        conn.connect();
        InputStream connectionInputStream = getConnectionInputStream(conn);
        BufferedReader reader = new BufferedReader(new InputStreamReader(connectionInputStream));
        String line;
        while ((line = reader.readLine()) != null) {
            responseString.append(line);
        }
        Log.d("GET Response >>", responseString.toString());
        if (conn.getResponseCode() >= 200 && conn.getResponseCode() <= 299) {
            getResponse = new Gson().fromJson(responseString.toString(), successClass);
        } else {
            getResponse = new Gson().fromJson(responseString.toString(), String.class);
        }
        conn.disconnect();
        return getResponse;
    }

    private HttpURLConnection getConnection(String type, URL requestUrl) throws Exception {
        HttpURLConnection conn = (HttpURLConnection) requestUrl.openConnection();
        conn.setDoOutput(false);
        conn.setRequestMethod(type);
        conn.setReadTimeout(READ_TIMEOUT);
        conn.setConnectTimeout(CONNECTION_TIMEOUT);
        conn.setRequestProperty("Content-Type", "application/json");
        conn.setRequestProperty("Accept-Encoding", "gzip,deflate");
        conn.setRequestProperty("accept", "application/json");
        return conn;
    }

    private InputStream getConnectionInputStream(HttpURLConnection httpURLConnection) throws Exception {
        InputStream in;
        if (httpURLConnection.getResponseCode() >= 200 && httpURLConnection.getResponseCode() <= 299) {
            in = new BufferedInputStream(httpURLConnection.getInputStream());
        } else {
            in = new BufferedInputStream(httpURLConnection.getErrorStream());
        }
        return in;
    }
}

Benchmark Results

This is first step to do the benchmark. Before doing this benchmark There are assumptions I have made.First one is both scenarios  same internet connection in the device.

To Complete One Request

Retrofit 2017-04-09 04:38:22.285 2017-04-09 04:38:22.731 [ 446 mili seconds]
Android 2017-04-09 04:39:19.321 2017-04-09 04:39:19.648 [ 321 mili seconds ]