I am working with small app that uses spring-hibernate but I am a newcomer to field of spring MVC, I have some questions:
1) using single controller for multiple pages is good practice or should I create separate cont. class for each page.
2) I don't want to use form tag of spring I'm using html forms.
My controller is as follows:
package com.servlets.controllers;
import com.utils.generalUtils;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.servlet.ModelAndView;
#Controller
public class signin{
#RequestMapping(value = "/login", method={RequestMethod.POST,RequestMethod.GET})
public ModelAndView loginForm(HttpServletRequest req,HttpServletResponse response){
HashMap<String,String> lsMsg = new HashMap<String,String>();
lsMsg = generalUtils.getInstance().LoginCheck(req);
for (Map.Entry<String, String> entry : lsMsg.entrySet()) {
String key = entry.getKey();
String value = entry.getValue();
System.out.println(" key -- "+key+" value -- "+value);
}
if((lsMsg.get("Authorized")).equals("true")){
return new ModelAndView("landing", "message", lsMsg);
}
return new ModelAndView("login", "message", lsMsg);
}
#RequestMapping(value = "/fergot", method={RequestMethod.POST,RequestMethod.GET})
public ModelAndView fergotForm(HttpServletRequest req) { // Not implemented yet
HashMap<String,String> lsMsg = new HashMap<String,String>();
return new ModelAndView("fergot", "message", lsMsg);
}
#RequestMapping("/register")
public ModelAndView registerForm(HttpServletRequest req,HttpServletResponse response) throws IOException{
HashMap<String,String> lsMsgs = new HashMap<String,String>();
lsMsgs.put("Authorized", "false");
lsMsgs = generalUtils.getInstance().addUser(req);
if((lsMsgs.get("Authorized")).equals("true")){
response.sendRedirect("login.html");
}
return new ModelAndView("register", "message", lsMsgs);
}
}
1) Both way are ok. But you want best practise, so my suggestion is create separate controller class for each page . This is common choice in most cases. On the other hand, if your app is very very small, even if you using single controller is also not a big problem.
Chose which way's core problem is which way can maintain easier in the future and which way is more readable for others.
2) You say you don't want to use spring form tag, ofcourse you can. Take it easy, You can not use any part of spring you don't want to use, it is ok.
Related
Good day to all, there is a problem, my bean is executed twice, and I can not understand why. I'm new to spring boot and I'm afraid it's about misused annotations.
package com.Alfa.controllers;
import com.Alfa.tools.JsonParser;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.*;
import java.io.FileNotFoundException;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.Calendar;
import java.util.Map;
#SpringBootApplication
#Controller
public class MainController {
private final RankClient rank;
private final GifClient gifClient;
#Autowired
public MainController(RankClient rank, GifClient gifClient) {
this.rank = rank;
this.gifClient = gifClient;
}
#GetMapping("/{currency}")
public String getGif(#PathVariable String currency, Model model) {
JsonParser jsonParser=new JsonParser();
System.out.println(currency);
boolean res = jsonParser.isRankHigherToday(rank.getYesterdayJson(getYesterdayDate(), currency).getBody(), rank.getJson(currency).getBody());
Map gif;
if (res == false) gif = gifClient.getBrokeGif().getBody();
else gif = gifClient.getRichGif().getBody();
gif = (Map) gif.get("data");
gif = (Map) gif.get("images");
gif = (Map) gif.get("downsized_large");
String GifSrc = (String) gif.get("url");
model.addAttribute("src", GifSrc);
return "index";
}
private String getYesterdayDate() {
final Calendar cal = Calendar.getInstance();
cal.add(Calendar.DATE, -1);
DateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd");
return dateFormat.format(cal.getTime());
}
}
#FeignClient(name = "RankClient", url = "${feign.client.rank.url}")
interface RankClient {
#RequestMapping(method = RequestMethod.GET, value = "${feign.client.rank.json.latest}"+"${feign.client.rank.token}&base={currency}")
ResponseEntity<Map> getJson(#PathVariable("currency") String currency);
#RequestMapping(method = RequestMethod.GET, value = "${feign.client.rank.json.historical}" +
"{date}" + "${feign.client.rank.json.historical.end}" + "${feign.client.rank.token}&base={currency}")
ResponseEntity<Map> getYesterdayJson(#PathVariable("date") String date, #PathVariable("currency") String currency);
}
#FeignClient(name = "GifClient", url = "${feign.client.gif.url}")
interface GifClient {
#RequestMapping(method = RequestMethod.GET, value = "${feign.client.gif.token}" + "${feign.client.gif.tag.broke}")
ResponseEntity<Map> getBrokeGif();
#RequestMapping(method = RequestMethod.GET, value = "${feign.client.gif.token}" + "${feign.client.gif.tag.rich}")
ResponseEntity<Map> getRichGif();
}
System.out.println(currency); in my case returns two values, the first is what I need, but then returns favicon. ico.
I do not know where favicon.ico comes from and would like to understand what I am doing wrong.
A web browser will make a request to /favicon.ico to retrieve the icon used to represent your web site in tabs, bookmark lists, etc. I suspect that's what's happening here.
You could avoid the problem by changing the URI so that your #GetMapping doesn't match anything beneath /. For example, you could use something like currencies/{currency}.
Alternatively, if you want or need to use /{currency}, you could validate that currency is a known currency code and return a 404 if it is not. Some input validation is generally a good idea, so that change is probably worth making no matter what URI the method is mapped to.
I have a couple of spring boot rest controllers, and I want a standard JSON response structure to be sent to the client.
The standard response will be composed of responseTime, apiResponseCode, status, apiName, response ( which will vary based on the api). See below:
{
"responseTime": "2020-04-19T08:36:53.001",
"responseStatus": "SUCCESS",
"apiResponseCode": "SUCCESS",
"apiName": "PROPERTY_STORE_GET_PROPERTIES",
"response": [
{
"propertyName": "app.name",
"propertyValue": "property-store"
}
]
}
To achieve this, I have created below model class:
package com.example.response.model;
import java.io.Serializable;
import java.time.LocalDateTime;
import com.example.constants.ApiResponseCode;
import com.example.constants.Status;
public class ApplicationResponse<T> implements Serializable {
private static final long serialVersionUID = -1715864978199998776L;
LocalDateTime responseTime;
Status responseStatus;
ApiResponseCode apiResponseCode;
String apiName;
T response;
public ApplicationResponse(LocalDateTime responseTime, Status status,
ApiResponseCode apiRespCode, String apiName, T response) {
this.responseTime = responseTime;
this.responseStatus = status;
this.apiResponseCode = apiRespCode;
this.apiName = apiName;
this.response = response;
}
// getters and setters
To create a generic response wrapper, I have created below response util class.
import java.time.LocalDateTime;
import com.example.constants.ApiResponseCode;
import com.example.constants.Status;
import com.example.response.model.ApplicationResponse;
public class ResponseUtil {
public static <T> ApplicationResponse<T> createApplicationResponse(String
apiName, T response) {
return new ApplicationResponse<>(LocalDateTime.now(),
Status.SUCCESS, ApiResponseCode.SUCCESS, apiName,
response);
}
private ResponseUtil() {
}
}
Now the ask is that my response from controller should get serialized in the standard way. Shown below is my controller method.
package com.example.propertystore.controller;
import java.util.List;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.web.bind.annotation.RestController;
import com.example.constants.ApiResponseCode;
import com.example.constants.Status;
import com.example.exception.ApplicationException;
import com.example.exception.ApplicationExceptionHelper;
import com.example.propertystore.constants.PropertyStoreApiName;
import com.example.propertystore.dto.PropertyDTO;
import com.example.propertystore.entity.Property;
import com.example.propertystore.service.PropertyStoreService;
import com.example.response.ResponseUtil;
import com.example.response.model.ApplicationResponse;
#RestController
public class PropertyStoreControllerImpl implements PropertyStoreController {
#Autowired
PropertyStoreService propertyStoreService;
#Autowired
ApplicationExceptionHelper exceptionHelper;
#Override
public ApplicationResponse<List<PropertyDTO>> getProperties() throws ApplicationException {
ApplicationResponse<List<PropertyDTO>> response = null;
try {
response = ResponseUtil.createApplicationResponse(
PropertyStoreApiName.PROPERTY_STORE_GET_PROPERTIES.toString(),
propertyStoreService.getProperties());
} catch (Exception e) {
exceptionHelper.raiseApplicationException( HttpStatus.INTERNAL_SERVER_ERROR, Status.FAILURE,
ApiResponseCode.INTERNAL_SERVER_ERROR,
PropertyStoreApiName.PROPERTY_STORE_GET_PROPERTIES.toString(), null);
}
return response;
}}
With the current implementation what I'll have to do is that in my controllers I will have to transform the response by calling ResponseUtil.createApplicationResponse(). This is going to litter the entire controller methods with the createApplicationResponse() method call.
What I wanted to explore is that if there is any cleaner way of achieving this using servlet filters or AOP?
PS: I tried filter option, but couldn't understand how to proceed around it. Got stuck after retrieving the response.getOutputStream() in doFilter().
Hope someone can help?
Just wrap all your responses into a decorator object.
class ResponseDecorator<T> {
//global.fields (time,code, status.....)
T response;
}
Then wrap this response wrapper into the ResponseEntity
The response.getOutputStream that you used and filters are servlet related classes , and i think you can do that without them.Just make your custom response class and add fields however you want your response. Than in the controller , just return new ResponseEntity(HttpStatus.OK,"your message "):
I don't know if this is the behavior you want.
I want to make a rest call to bitbucket api with ssh private key as header.For example the url is (http://bitbucket.com/rest/api/1.0/repos/testProject/pull-requests?state=OPEN).
Is there a way call this url with spring rest template and how to pass ssl access key as header.
Instead of using SSH keys use Personal Access Tokens:
https://confluence.atlassian.com/bitbucketserver/personal-access-tokens-939515499.html
(Introduced in Bitbucket 5.5)
Then you could use code like this:
package com.company.bitbucket.tools.application;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.StringJoiner;
import java.util.stream.Collectors;
import org.springframework.http.client.ClientHttpRequestInterceptor;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.client.RestTemplate;
import org.springframework.web.servlet.ModelAndView;
import com.company.bitbucket.tools.HttpRequestInterceptor;
import com.company.bitbucket.tools.ProjectValue;
import com.company.bitbucket.tools.Projects;
import com.company.bitbucket.tools.UserValue;
import com.company.bitbucket.tools.Users;
#Controller
public class ProjectController {
public static String BITBUCKET_URL = "https://bitbucket.company.com/rest/api/latest/";
public static String PROJECTS = "projects";
public static String PERMISSIONS = "permissions/users?permission=PROJECT_ADMIN";
public static String PAT = "<put your generated token in here>";
#RequestMapping(value={"/projects"}, method = RequestMethod.GET)
public ModelAndView listProjects(){
HashMap<String, String> list = getAdmins();
ModelAndView model = new ModelAndView("projects");
model.addObject("adminMap", list);
return model;
}
private HashMap<String, String> getAdmins(){
HashMap<String, String> projectMap = new HashMap<>();
RestTemplate restTemplate = new RestTemplate();
List<ClientHttpRequestInterceptor> interceptors = new ArrayList<ClientHttpRequestInterceptor>();
interceptors.add(new HttpRequestInterceptor("Authorization", "Bearer ".concat(PAT)));
restTemplate.setInterceptors(interceptors);
Projects projects = restTemplate.getForObject(BITBUCKET_URL.concat("projects?limit=100"), Projects.class);
for(ProjectValue projectValue: projects.getValues()) {
String projectUrl = String.format("%s/%s/%s/%s", BITBUCKET_URL, PROJECTS, projectValue.getKey(), PERMISSIONS);
Users users = restTemplate.getForObject(projectUrl, Users.class);
List<String> names = new ArrayList<>();
for (UserValue value: users.getValues()) {
names.add(value.getUser().getDisplayName());
}
String commaSeparatedNames = String.join(", ", names);
projectMap.put(projectValue.getName(), commaSeparatedNames);
}
return projectMap;
}
}
This code gets a list of project admins using the rest api, but you could change to make whatever rest request you wish.
I have a mapping error to my application. As you can see from the code I have the
#RequestMapping("/productList")
but when I am on my homepage and hover the link Products (for /productList) on the bottom left on my browser the url is the following
localhost:8080/eMusiscStore/productList;jsessionid=B16DF0DE6E0089AC5F7DBE356181BBB1
So sometimes, the page cannot be displayed and other times the requested page (/productList) loads normally.
What can I do to be sure that my mapping will be correct every time?
I post only my HomeController. Let me know if you need another file.
package com.controllers;
import java.io.IOException;
import java.util.List;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import com.entities.Product;
import com.servicesapi.ProductService;
#Controller
public class HomeController {
#Autowired
ProductService productService;
#RequestMapping("/")
public String home(){
System.out.println("HomeController home");
return "home";
}
#RequestMapping("/productList")
public String getProducts(Model model) {
System.out.println("HomeController productiList");
List<Product> products = productService.getAllProducts();
model.addAttribute("products", products);
return "productList";
}
#RequestMapping("/productList/viewProduct/{productId}")
public String viewProduct(#PathVariable String productId, Model model) throws IOException{
Product product = productService.getProductById(productId);
model.addAttribute(product);
return "viewProduct";
}
}
As you have mentioned in the description that some time you are able to render your productList page.This could a bad cache issue.You can either try running your page incognito mode or clear cache always.Also to be double sure that the your rest call is working fine install postman plugin in your chrome browser and try invoking localhost:8080/eMusiscStore/productList;jsessionid=B16DF0DE6E0089AC5F7DBE356181BBB1
If everything goes fine this should return you productList in the response body. Aren't you mentioning the type of CRUD operation?Looks like it is a #get method.
I have a put method that accepts inputstream. I want to call this method using rest assured in JUnit.
This is what I used:
with().body(inpustream).put("/service/1"); // i got error 404 forbidden.
POST will return status code 201 and PUT will return 200, and POST will create a new resource but, PUT will update the existing resource. This means we will have to mention which resource we wish to update in the URI itself like below.
import io.restassured.RestAssured;
import static io.restassured.RestAssured.*;
import java.util.HashMap;
import java.util.Map;
import org.testng.annotations.BeforeTest;
import org.testng.annotations.Test;
import static org.hamcrest.Matchers.*;
public class PUTMethod {
public static Map<String, String> map = new HashMap<String, String>();
#BeforeTest
public void putdata(){
map.put("userId", "2");
map.put("id", "19");
map.put("title", "this is projectdebug.com");
map.put("body", "i am testing REST api with REST-Assured and sending a PUT request.");
RestAssured.baseURI = "http://jsonplaceholder.typicode.com";
RestAssured.basePath = "/posts/";
}
#Test
public void testPUT(){
given()
.contentType("application/json")
.body(map)
.when()
.put("/100")
.then()
.statusCode(200)
.and()
.body("title", equalTo("this is projectdebug.com"));
}
}
Visit http://www.projectdebug.com/send-put-request-using-rest-assured/
for more information.
Actually, you are doing well but sending multipart through PUT is unsecured and is quite random (https://jira.spring.io/browse/SPR-9079). Amend your spring-security.xml to add a filter or use POST method in this case.
You can also try your code by calling another PUT webservice with no stream.
(And what is the error code ? 404 or 403 ?)
A similar problem solved by using MultipartFilter : Spring 3.0 FileUpload only with POST?
Have a look at the following example, where it explains how to use PUT request using Rest Assured:
import org.testng.Assert;
import org.testng.annotations.BeforeClass;
import org.testng.annotations.Test;
import static com.jayway.restassured.RestAssured.*;
import com.jayway.restassured.RestAssured;
import com.jayway.restassured.response.Response;
public class GetStatusCodeTest {
#BeforeClass
public void setBaseUri () {
RestAssured.baseURI = "https://localhost:3000";
}
#Test
public void updateUsingPut () {
Posts post = new Posts();
post.setId ("3");
post.setTitle ("Hello Bhutan");
post.setAuthor ("StaffWriter");
given().body (post)
.when ()
.contentType (ContentType.JSON)
.put ("/posts/3");
}
}
For detailed explanation, you may check out the following link:
https://restservicestesting.blogspot.in/2016/10/automating-put-request-using-rest.html