I want to create a service in order to populate dropdown from database. I tried this:
Merchant Class:
export class Merchant {
constructor(
public id: string,
public name: string,
public state_raw: string,
public users: string,
) {}
}
Merchant Service:
getList(): Observable<Merchant> {
return this.http.get<Merchant>(environment.api.urls.merchants.base, {});
}
Rest Api impl:
#GetMapping
public ResponseEntity<?> get() {
return merchantRepository
.findAll()
.map(mapper::toDTO)
.map(ResponseEntity::ok)
.orElseGet(() -> notFound().build());
}
SQL query:
#Override
public Iterable<Merchants> findAll() {
String hql = "select e from " + Merchants.class.getName() + " e";
TypedQuery<Merchants> query = entityManager.createQuery(hql, Merchants.class);
List<Merchants> merchants = query.getResultList();
return merchants;
}
But I get this error:
The method map(mapper::toDTO) is undefined for the type Iterable<Merchants>
How should I implement properly this mapping for the response?
Seems like you meant to stream the entities.
#GetMapping
public ResponseEntity<?> get() {
return StreamSupport.stream(merchantRepository.findAll().spliterator(), false)
.map(mapper::toDTO)
.map(ResponseEntity::ok)
.findFirst()
.orElseGet(() -> notFound().build());
}
Related
I am new to Reactive programming and I got stuck writing a custom Insert query.
So far I have a FriendshipRepository.java class.
public interface FriendshipRepository extends R2dbcRepository<Friendship, String> {
#Query(value = "INSERT INTO public.friendship(requester_id, addressee_id) values (:requesterid::uuid, :addresseeid::uuid)")
public Mono<Void> insertFriendRequest(
#Param("requesterid") String requesterId,
#Param("addresseeid") String addresseeId
);
}
And a FriendshipController.java class.
#RestController
public class FriendsController {
private final FriendshipRepository friendshipRepository;
public FriendsController(FriendshipRepository friendshipRepository) {
this.friendshipRepository = friendshipRepository;
}
#PostMapping(value = "/request", produces = "application/json")
public Mono<ResponseEntity<RequestResponse>> sendFriendRequest(#RequestBody FriendRequest friendRequest, #AuthenticationPrincipal Mono<User> principal) throws Exception {
String id = principal.map(User::getId).toFuture().get();
return friendshipRepository.insertFriendRequest(id, friendRequest.getUserId()).log()
.then(Mono.just("NEXT"))
.map(e -> {
return ResponseEntity.status(HttpStatus.CREATED).body(new RequestResponse("Success", ResponseCode.SUCCESS));
}).onErrorResume(e -> {
return Mono.just(ResponseEntity.status(HttpStatus.FORBIDDEN).body(new RequestResponse("Friend request was unsuccessful ", ResponseCode.REFUSED)));
});
}
}
This is a working example.
But I dont understand why I have to call .then(Mono.just("NEXT")) and create a new Mono to be able to return a custom ResponseEntity<RequestResponse>>. I also tried merge the the whole process. I meen by this at the begining when I get the Id from the ReactiveSpringSecutiryContext that is a blocking line of code and If I know it correctly that is a bad approach in Reactive programming.
I tried this approach but in this case, I can only retrun the Id of the user.
Mono<String> userId = principal.map(User::getId);
return userId.doOnNext(id -> {
friendshipRepository.insertFriendRequest(id, friendRequest.getUserId()).log()
.map(e -> {
return ResponseEntity.status(HttpStatus.CREATED).body(new RequestResponse("SIKER", ResponseCode.SUCCESS));
})
.onErrorResume(e -> {
return Mono.just(ResponseEntity.status(HttpStatus.FORBIDDEN).body(new RequestResponse("Friend request was unsuccessful ", ResponseCode.REFUSED)));
}).subscribe();
// .doOnSuccess(e -> ServerResponse.noContent().build((new RequestResponse("SIKER", ResponseCode.SUCCESS)), Void.class));
});
How could I rewrite this endpoint? Or does my whole approach inaproptirate?
Thank you in advance for your help.
Probably not the nicest solution, but better then it was.
My query:
public interface FriendshipRepository extends R2dbcRepository<Friendship, String> {
#Query(value = "INSERT INTO public.friendship(requester_id, addressee_id) values (:requesterid::uuid, :addresseeid::uuid) RETURNING id")
public Mono<String> insertFriendRequest(
#Param("requesterid") String requesterId,
#Param("addresseeid") String addresseeId
);
}
My api:
#PostMapping(value = "/request", produces = "application/json")
public Mono<ResponseEntity<RequestResponse>> sendFriendRequest(#RequestBody FriendRequest friendRequest, #AuthenticationPrincipal Mono<User> principal) throws Exception {
String id = principal.map(User::getId).toFuture().get();
return friendshipRepository.insertFriendRequest(id, friendRequest.getUserId()).log()
.map(e -> {
return ResponseEntity.status(HttpStatus.CREATED).body(new RequestResponse("Success", ResponseCode.SUCCESS));
}).onErrorResume(e -> {
return Mono.just(ResponseEntity.status(HttpStatus.FORBIDDEN).body(new RequestResponse("Friend request was unsuccessful ", ResponseCode.REFUSED)));
});
}
Assuming I've the following endpoints in spring boot
GET /todo
DELETE /todo/{id}
How can ensure that only entries for the userid are returned and that the user can only update his own todos?
I've a populated Authentication object.
Is there any build in way I can use? Or just make sure to always call findXyzByIdAndUserId where userid is always retrieved from the Principal?
I'm a bit worried about the possibility to forget the check and displaying entries from other users.
My approach to this would be a 3 way implementation: (using jpa & hibernate)
a user request context
a mapped superclass to get your context
a statement inspector to inject your userid
For example:
public final class UserRequestContext {
public static String getUserId() {
// code to retrieve your userid and throw when there is none!
if (userId == null) throw new IllegalStateException("userid null");
return userId;
}
}
#MappedSuperclass
public class UserResolver {
public static final String USER_RESOLVER = "USER_RESOLVER";
#Access(AccessType.PROPERTY)
public String getUserId() {
return UserRequestContext.getUserId();
}
}
#Component
public class UserInspector implements StatementInspector {
#Override
public String inspect(String statement) {
if (statement.contains(UserResolver.USER_RESOLVER)) {
statement = statement.replace(UserResolver.USER_RESOLVER, "userId = '" + UserRequestContext.getUserId() + "'" );
}
return sql;
}
#Bean
public HibernatePropertyCustomizer hibernatePropertyCustomizer() {
return hibernateProperies -> hibernateProperties.put("hibernate.session_factory.statement_inspector",
UserInspector.class.getName());
}
}
So your Entity looks like this:
#Entity
...
#Where(clause = UserResolver.USER_RESOLVER)
public class Todo extends UserResolver {
...
}
In Spring Reactive Java how can I write an updateById() method using the Router and Handler?
For example, the Router has this code:
RouterFunctions.route(RequestPredicates.PUT("/employees/{id}").and(RequestPredicates.accept(MediaType.APPLICATION_JSON))
.and(RequestPredicates.contentType(MediaType.APPLICATION_JSON)),
employeeHandler::updateEmployeeById);
My question is how to write the employeeHandler::updateEmployeeById() keeping the ID as same but changing the other members of the Employee object?
public Mono<ServerResponse> updateEmployeeById(ServerRequest serverRequest) {
Mono<Employee> employeeMono = serverRequest.bodyToMono(Employee.class);
<And now what??>
return ServerResponse.ok().contentType(MediaType.APPLICATION_JSON).body(employeeMono, Employee.class);
}
The Employee class looks like this:
#Document
#Data
#AllArgsConstructor
#NoArgsConstructor
public class Employee {
#Id
int id;
double salary;
}
Thanks for any help.
First of all, you have to add ReactiveMongoRepository in your classpath. You can also read about it here.
#Repository
public interface EmployeeRepository extends ReactiveMongoRepository<Employee, Integer> {
Mono<Employee> findById(Integer id);
}
Then your updateEmployeeById method can have the following structure:
public Mono<ServerResponse> updateEmployeeById(ServerRequest serverRequest) {
return serverRequest
.bodyToMono(Employee.class)
.doOnSubscribe(e -> log.info("update employee request received"))
.flatMap(employee -> {
Integer id = Integer.parseInt(serverRequest.pathVariable("id"));
return employeeRepository
.findById(id)
.switchIfEmpty(Mono.error(new NotFoundException("employee with " + id + " has not been found")))
// what you need to do is to update already found entity with
// new values. Usually map() function is used for that purpose
// because map is about 'transformation' what is setting new
// values in our case
.map(foundEmployee -> {
foundEmployee.setSalary(employee.getSalary());
return foundEmployee;
});
})
.flatMap(employeeRepository::save)
.doOnError(error -> log.error("error while updating employee", error))
.doOnSuccess(e -> log.info("employee [{}] has been updated", e.getId()))
.flatMap(employee -> ServerResponse.ok().contentType(MediaType.APPLICATION_JSON).body(BodyInserters.fromValue(employee), Employee.class));
}
UPDATE:
Based on Prana's answer, I have updated the code above merging our solutions in one. Logging with a help of Slf4j was added. And switchIfEmpty() functions for the case when the entity was not found.
I would also suggest your reading about global exception handling which will make your API even better. A part of it I can provide here:
/**
* Returns routing function.
*
* #param errorAttributes errorAttributes
* #return routing function
*/
#Override
protected RouterFunction<ServerResponse> getRoutingFunction(ErrorAttributes errorAttributes) {
return RouterFunctions.route(RequestPredicates.all(), this::renderErrorResponse);
}
private HttpStatus getStatus(Throwable error) {
HttpStatus status;
if (error instanceof NotFoundException) {
status = NOT_FOUND;
} else if (error instanceof ValidationException) {
status = BAD_REQUEST;
} else {
status = INTERNAL_SERVER_ERROR;
}
return status;
}
/**
* Custom global error handler.
*
* #param request request
* #return response
*/
private Mono<ServerResponse> renderErrorResponse(ServerRequest request) {
Map<String, Object> errorPropertiesMap = getErrorAttributes(request, false);
Throwable error = getError(request);
HttpStatus errorStatus = getStatus(error);
return ServerResponse
.status(errorStatus)
.contentType(APPLICATION_JSON)
.body(BodyInserters.fromValue(errorPropertiesMap));
}
A slightly different version of the above worked without any exceptions:
public Mono<ServerResponse> updateEmployeeById(ServerRequest serverRequest) {
Mono<ServerResponse> notFound = ServerResponse.notFound().build();
Mono<Employee> employeeMono = serverRequest.bodyToMono(Employee.class);
Integer employeeId = Integer.parseInt(serverRequest.pathVariable("id"));
employeeMono = employeeMono.flatMap(employee -> employeeRepository.findById(employeeId)
.map(foundEmployee -> {
foundEmployee.setSalary(employee.getSalary());
return foundEmployee;
})
.flatMap(employeeRepository::save));
return ServerResponse.ok().contentType(MediaType.APPLICATION_JSON).body(employeeMono, Employee.class).switchIfEmpty(notFound);
}
Thanks to Stepan Tsybulski.
I'm using Angular CLI and Spring Boot.
Everything works (getEmployee, deleteEmployee) but when I want to Update or Create (same method, and same HTML Form) an Employee I get in HTML Console/Network this error:
[ERROR] message: "Request method 'PUT' not supported"
This is my Controller:
#RestController
#RequestMapping("/api")
public class EmployeeController {
private final EmployeeServiceImpl employeeService;
#Autowired
public EmployeeController(EmployeeServiceImpl employeeService) {
this.employeeService = employeeService;
}
#GetMapping("/employees")
public List<Employee> getAllEmployees() {
return employeeService.findAllEmployees();
}
#GetMapping("/employees/{id}")
public ResponseEntity<Employee> getEmployeeById(#PathVariable(value = "id") Long employeeId) {
Employee employee = employeeService.findById(employeeId).get();
return ResponseEntity.ok().body(employee);
}
#PutMapping("/employees/{id}")
public ResponseEntity<Employee> updateEmployee(#PathVariable(value = "id") Long employeeId,
#Valid #RequestBody Employee employeeDetails) {
Employee employee = employeeService.findById(employeeId).get();
employee.setEmailAddress(employeeDetails.getEmailAddress());
employee.setLastName(employeeDetails.getLastName());
employee.setFirstName(employeeDetails.getFirstName());
employee.setStatus(employeeDetails.getStatus());
employee.setSkills(employeeDetails.getSkills());
final Employee updatedEmployee = employeeService.saveEmployee(employee);
return ResponseEntity.ok(updatedEmployee);
}
#DeleteMapping("/employees/{id}")
public Map<String, Boolean> deleteEmployee(#PathVariable(value = "id") Long employeeId) {
Employee employee = employeeService.findById(employeeId).get();
employeeService.deleteEmployee(employee);
Map<String, Boolean> response = new HashMap<>();
response.put("deleted", Boolean.TRUE);
return response;
}
}//close class
This is also my CORSConfig:
#Configuration
public class CORSConfiguration implements WebMvcConfigurer {
#Override
public void addCorsMappings(CorsRegistry registry) {
registry.addMapping("/**").allowedHeaders("*")
.allowedMethods("GET", "POST", "DELETE","PUT");
}
}//close class
Here you can see my Angular code:
EmployeeDetail.ts
export class EmployeeDetailsComponent implements OnInit {
#Input()
employee: Employee;
skills;
constructor(
private route: ActivatedRoute, /* holds information about the route to this instance of the EmployeeDetailsComponent */
private location: Location,
private employeeService: EmployeeService,
private skillService: SkillService
) {
}
ngOnInit() {
this.getEmployee();
this.skillService.getSkills().subscribe(res => this.skills = res);
}
getEmployee(): void {
const id = +this.route.snapshot.paramMap.get('id');
if (id === -1) {
this.employee = new Employee();
} else {
this.employeeService.getEmployee(id)
.subscribe(employee => this.employee = employee);
}
}
save(): void {
this.employeeService.updateEmployee(this.employee)
.subscribe(() => this.goBack());
console.log('test', this.employee);
}
goBack(): void {
this.location.back();
}
}
When I click on save the method redirect me to the Update method in my service;
service.ts
const httpOptions = {
headers: new HttpHeaders({'Content-Type': 'application/json'})
};
#Injectable({
providedIn: 'root'
})
export class EmployeeService {
private baseUrl = 'http://localhost:8080/api/employees';
constructor(private http: HttpClient) { }
/** GET Employees from the server */
getEmployees(): Observable<Employee[]> {
return this.http.get<Employee[]>(this.baseUrl);
}
getEmployee(id: number): Observable<Employee> {
const url = this.baseUrl + '/' + id;
return this.http.get<Employee>(url);
}
/** PUT: update the employee on the server */
updateEmployee(employee: Employee): Observable<any> {
return this.http.put(this.baseUrl, employee, httpOptions);
}
deleteEmployee(id: number): Observable<any> {
return this.http.delete(`${this.baseUrl}/${id}`, { responseType: 'text' });
}
}
This is my first SpringBoot app, also w/Angular so I never seen this error before.
What can I do?
Your Angular code shows the following method
updateEmployee(employee: Employee): Observable<any> {
return this.http.put(this.baseUrl, employee, httpOptions);
}
You did not include id in angular side. But for your spring boot side requires id. Since you are not passing the id the framework cannot find matching method resulting in 405 method not allowed error
#PutMapping("/employees/{id}")
public ResponseEntity<Employee> updateEmployee(#PathVariable(value = "id") Long employeeId,
#Valid #RequestBody Employee employeeDetails) {
Employee employee = employeeService.findById(employeeId).get();
employee.setEmailAddress(employeeDetails.getEmailAddress());
employee.setLastName(employeeDetails.getLastName());
employee.setFirstName(employeeDetails.getFirstName());
employee.setStatus(employeeDetails.getStatus());
employee.setSkills(employeeDetails.getSkills());
final Employee updatedEmployee = employeeService.saveEmployee(employee);
return ResponseEntity.ok(updatedEmployee);
}
I'm trying to create a #select input for a enum field. Everything works fine until the form is submitted. It fails with a weird validation error -> "error.invalid"
here's my code
Enum class
package model;
...
public enum UserType {
UserType_Admin("Administrator"), UserType_Monitor("Monitor"), UserType_Audit("Audit");
private String desc;
private UserType(String desc) {
this.desc = desc;
}
#Override
public String toString() {
return Messages.get(desc);
}
public String getLabel() {
return toString();
}
public String getKey() {
return super.toString();
}
public static Map<String, String> options() {
LinkedHashMap<String, String> options = new LinkedHashMap<String, String>();
for (UserType ut : UserType.values()) {
Integer o = ut.ordinal();
options.put(o.toString(), ut.desc);
}
return options;
}
}
My Entity
#Entity
public class User extends Model {
...
#Id
public Long userID;
public UserType user_type;
}
Scala template
#form(routes.Users.save(userID)) {
#select(
userForm("user_type"),
options(model.UserType.options),
'_label -> Messages("UserType"), '_default -> Messages("choose_user_type"),
'_showConstraints -> true
)
}
on the controller the Save method:
public static Result save(Long userID) {
Form<User> userForm = form(User.class).bindFromRequest();
if (userForm.hasErrors()) { <- here it says that has errors
return badRequest(useredit.render(new Session(session()), userID,
userForm, new User()));
}
...
}
if I inspect the userForm variable, I get:
Form(of=class model.User, data={user_type=0}, value=None,
errors={user_type=[ValidationError(user_type,error.invalid,[])]})
The field user_type has the correct value, 0 if I choose the first item, 1 for the second, etc.
Screenshot
Anyone has a clue or a workaround for this? Maybe disable validation for this field? Tks guys