Spring IOC DI with runtime parameters - java

I'm relativity new to IOC and DI, so I'm guessing that I am missing some high-level design principle here, but I cannot figure out how to get my architecture working.
I have a REST API endpoint that accepts two pieces of POST data: customer ID, and Type ID. The rest api then needs to return a set of data for that specific customer/type combo.
Here is a crude picture of what I am doing:
The controller is taking the entity IDs passed in via post data, and via a JPA repository getting the appropriate Entities for them.
I then construct a data generator object (that takes the entities as constructor parameters), and use that to handle all of the data gathering for the API.
The Problem: because the Data Generator takes the two dynamic constructor parameters, it cannot be DI'ed into the Controller, but instead must be made with new. Inside of the Data Generator, however, I need access to JPA repositories. The only way to get access to these repositories is via DI. I cannot DI however, as the object was new'ed not DI'ed by the IOC container.
Is there a way to architect this so that I don't have this problem? Am I breaking some rule regarding IOC? Do I have wrong assumptions somewhere? Any advice is appreciated.
Thanks!
Edit: Pseudo code for Data Generator
public class DataGenerator {
private Customer customer;
private Type type
public DataGenerator(Customer customer, Type type) {
this.cusomter = customer;
this.type = type;
}
public generateData() {
if(customer == x && type == y) {
//JPA REPOSITORY QUERY
} else {
//DIFFERENT JPA REPOSITORY QUERY
}
}
}

I think you may have gotten confused somewhere along the line. You should have a Service that hits your repositories, and provide the information to the controller. One crude setup would be something like this.
#Controller
public MyController {
#AutoWired
private DataService dataService;
#RequestMapping(value = "/", method = RequestMethod.GET)
private DataGenerator readBookmark(#PathVariable Long customerId, #PathVariable Integer typeId) {
return dataService.getData(customerId, typeId);
}
}
#Service
public class DataService {
#AutoWired
private JPARepository repository;
public DataGenerator getData(long customerId, int typeId) {
Type typeDetails = repository.getType(typeId);
Customer customerDetails = repository.getCustomer(customerId);
return new DataGenerator(customerDetails, typeDetails);
}
}

Related

How to map a request in Spring MVC to a Java Object

I'm new to Spring, and since Spring provides many ways to map an HTTP request to Java objects, I'm hoping someone could advice me how to resolve this:
I have a client that sends a request having
ContentType: application/x-www-form-urlencoded
Some of the request parmeters have names such as
"form_data[orderStatus]", "form_data[orderNumber]", etc'
I have no control over this client!
I have a java class (#Component) called MyOrder which looks as follows:
#component
#Scope("prototpe")
public class MyOrder {
private String orderStatus;
private String orderNumber;
//etc'
public void setOrderStatus(String orderStatus) {
this.orderStatus = orderStatus;
}
//public setter for all other properties, as the above
}
What is the simplest way to create an instance of MyOrder
populated with all values of all "form_data[]", so that I can have a controller method having a signature that includes a MyOrder parameter, such as:
public ModelAndView saveNewOrder( #RequestParam("foo") String foo,
#ModelAttribute("myOrder") MyOrder anOrder) {
//... impl'n here
}
The best solution I could think of was to use a Web Filter which would flaten request params names such as "form_data[attrib1]" to "attrib1", and then Spring would do all the work of populating the MyOrder instance.
One disadvantage of this is that the request may have both "form_data[attrib1]" and "attrib1" parameters. For example:
form_data[orderStatus]=ok
orderStatus=fail
In this case i want MyOrder.orderStatus to have the value "ok".
Any good way of utilizing Spring create MyOrder from the request?
As an alternative, that does not use the class MyOrder, is there a way to have Spring map all the form_data[] parameters and their values to a map, so that i can have the controller method below?
public ModelAndView saveNewOrder( #RequestParam("foo") String foo,
<some annotation> #Map<String,String> formFieldsOfAnOrder) {
//... impl'n here
orderStatus = formFieldsOfAnOrder.get("orderStatus");
//or at least:
orderStatus = formFieldsOfAnOrder.get("form_data[orderStatus]");
}

Spring MVC (RESTful API): Validating payload dependent on a path variable

Use Case:
let's design a RESTful create operation using POST HTTP verb - creating tickets where creator (assigner) specifies a ticket assignee
we're creating a new "ticket" on following location: /companyId/userId/ticket
we're providing ticket body containing assigneeId:
{
"assigneeId": 10
}
we need to validate that assigneeId belongs to company in URL - companyId path variable
So far:
#RequestMapping(value="/{companyId}/{userId}/ticket", method=POST)
public void createTicket(#Valid #RequestBody Ticket newTicket, #PathVariable Long companyId, #PathVariable Long userId) {
...
}
we can easily specify a custom Validator (TicketValidator) (even with dependencies) and validate Ticket instance
we can't easily pass companyId to this validator though! We need to verify that ticket.assigneeId belongs to company with companyId.
Desired output:
ability to access path variables in custom Validators
Any ideas how do I achieve the desired output here?
If we assume that our custom validator knows desired property name, then we can do something like this:
Approach one:
1) We can move this getting path variables logic to some kind of a base validator:
public abstract class BaseValidator implements Validator {
#Override
public boolean supports(Class<?> clazz)
{
// supports logic
}
#Override
public void validate(Object target, Errors errors)
{
// some base validation logic or empty if there isn't any
}
protected String getPathVariable(String name) {
// Getting current request (Can be autowired - depends on your implementation)
HttpServletRequest req = HttpServletRequest((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();
if (req != null) {
// getting variables map from current request
Map<String, String> variables = req.getAttribute(HandlerMapping.URI_TEMPLATE_VARIABLES_ATTRIBUTE);
return variables.get(name);
}
return null;
}
}
2) Extend it with your TicketValidator implementation:
public class TicketValidator extends BaseValidator {
#Override
public void validate(Object target, Errors errors)
{
// Getting our companyId var
String companyId = getPathVariable("companyId");
...
// proceed with your validation logic. Note, that all path variables
// is `String`, so you're going to have to cast them (you can do
// this in `BaseValidator` though, by passing `Class` to which you
// want to cast it as a method param). You can also get `null` from
// `getPathVariable` method - you might want to handle it too somehow
}
}
Approach two:
I think it worth to mention that you can use #PreAuthorize annotation with SpEL to do this kind of validation (You can pass path variables and request body to it). You'll be getting HTTP 403 code though if validation woudnt pass, so I guess it's not exaclty what you want.
You could always do this:
#Controller
public class MyController {
#Autowired
private TicketValidator ticketValidator;
#RequestMapping(value="/{companyId}/{userId}/ticket", method=POST)
public void createTicket(#RequestBody Ticket newTicket,
#PathVariable Long companyId, #PathVariable Long userId) {
ticketValidator.validate(newTicket, companyId, userId);
// do whatever
}
}
Edit in response to the comment:
It doesn't make sense to validate Ticket independently of companyId when the validity of Ticket depends on companyId.
If you cannot use the solution above, consider grouping Ticket with companyId in a DTO, and changing the mapping like this:
#Controller
public class MyController {
#RequestMapping(value="/{userId}/ticket", method=POST)
public void createTicket(#Valid #RequestBody TicketDTO ticketDto,
#PathVariable Long userId) {
// do whatever
}
}
public class TicketDTO {
private Ticket ticket;
private Long companyId;
// setters & getters
}

Custom Logic with Injected Values when using Spring Data Rest

I want to convert an existing web service to take advantage of spring-data-rest.
How can I implement custom logic with injected values (specifically an OAuth2 Principal) on top of spring data rest to keep existing functionality ?
For example, say I want to override the GET method for /person/1 to contact an auditing web service before it goes on to return the data of person 1.
Right now, before using spring-data-rest, I would have:
#RequestMapping(value = "/person/{id}", method = RequestMethod.GET)
public void getPerson(#RequestBody ...., Principal principal)
{
/* Let Big Brother know that principal.getName() is doing this */
return thePerson;
}
How would I do something like this with Spring Data Rest generated endpoints?
Thanks for the suggestions. I found out that what works best (for me) in this case is what Dave Syer suggested and to just go with AspectJ.
In the end this will allow you to add custom logging logic, or anything else really, to the methods of a Spring Data JPA Repository:
#Component
#Aspect
public class MyRepositoryAuditor {
// pointcut on all methods with any arguments inside MyRepository
#Pointcut("execution(public * example.jpa.repositories.MyRepository.*(..) )")
public void publicRepositoryMethod() {
}
// if they return normally
#AfterReturning("publicRepositoryMethod()")
public void publicRepositoryMethod(JoinPoint jp) throws Throwable {
String methodName = jp.getSignature().getName();
.... perform logging ....
}
}

Spring Controller: use domain objects as #RequestBody

I have a domain object class User (it is a JPA entity):
#Entity
public class User {
private String name;
private boolean enabled = true;
// getters/setters
}
And I am trying to offer a REST API to allow clients to create new users, using Spring 3 MVC:
#Controller
public class UserController {
#RequestMapping(value="/user", method=RequestMethod.POST)
#ResponseBody
public String createRealm(#RequestBody User user) {
user.setEnabled(true); // client is not allowed to modify this field
userService.createUser(user);
...
}
}
It works great, but I do not know if it is a good idea to use the domain objects as #RequestBody, because I have to protect some fields that should not be directly modified by the client (i.e. "enabled" in this case).
What are the pros/cons of these alternatives:
Use the domain objects and protect the fields the user is not allowed to modify (for example set them to null or to its default value by hand)
Use a new set of auxiliar objects (something similar to a DTO), such as a UserRequest that only contains the fields I want to expose through the REST API, and map them (i.e. with Dozer) to the domain objects.
The second alternative looks like this:
#Entity
public class User {
private String name;
private boolean enabled = true;
// getters/setters
}
public class UserRequest {
private String name;
// enabled is removed
// getters/setters
}
#Controller
public class UserController {
#RequestMapping(value="/user", method=RequestMethod.POST)
#ResponseBody
public String createRealm(#RequestBody UserRequest userRequest) {
User user = ... // map UserRequest -> User
userService.createUser(user);
...
}
}
Is there any other way that avoids code duplication and is easier to maintain?
There is another option - you can disallow the submission of a given set of properties, using the DataBinder.setDisallowedFields(..) (or using .setAllowedFields(..))
#InitBinder
public void initBinder(WebDataBinder binder) {
binder.setDisallowedFields(..);
}
This is fine if you have one or two properties that differ.
Otherwise, having a special object (like ProfileDetails or UserRequest) makes more sense. I am using such a DTO-like object for this scenario and then transfer the fields with BeanUtils.copyProperties(..) from commons-beanutils
A third, perhaps better option, is to put all profile-related fields into a separate entity (mapped with #OneToOne with user) or to an #Embeddable object, and use it instead.

Data binding of an abstract class in spring-mvc

I've went thru Spring documentation and source code and still haven't found answer to my question.
I have these classes in my domain model and want to use them as backing form objects in spring-mvc.
public abstract class Credentials {
private Long id;
....
}
public class UserPasswordCredentials extends Credentials {
private String username;
private String password;
....
}
public class UserAccount {
private Long id;
private String name;
private Credentials credentials;
....
}
My controller:
#Controller
public class UserAccountController
{
#RequestMapping(value = "/saveAccount", method = RequestMethod.POST)
public #ResponseBody Long saveAccount(#Valid UserAccount account)
{
//persist in DB
return account.id;
}
#RequestMapping(value = "/listAccounts", method = RequestMethod.GET)
public String listAccounts()
{
//get all accounts from DB
return "views/list_accounts";
}
....
}
On UI I have dynamic form for the different credential types. My POST request usually looks like:
name name
credentials_type user_name
credentials.password password
credentials.username username
Following exception is thrown if I try to submit request to the server :
org.springframework.beans.NullValueInNestedPathException: Invalid property 'credentials' of bean class [*.*.domain.UserAccount]: Could not instantiate property type [*.*.domain.Credentials] to auto-grow nested property path: java.lang.InstantiationException
org.springframework.beans.BeanWrapperImpl.newValue(BeanWrapperImpl.java:628)
My initial thought was to use #ModelAttribute
#ModelAttribute
public PublisherAccount prepareUserAccountBean(#RequestParam("credentials_type") String credentialsType){
UserAccount userAccount = new PublisherAccount();
Class credClass = //figure out correct credentials class;
userAccount.setCredentials(BeanUtils.instantiate(credClass));
return userAccount;
}
Problem with this approach is that prepareUserAccountBean method get called before any other methods (like listAccounts) as well which is not appropriate.
One robust solution is to move out both prepareUserAccountBean and saveUserAccount to the separate Controller. It doesn't sound right : I want all user-related operations to reside in the same controller class.
Any simple solution? Can I utilize somehow DataBinder, PropertyEditor or WebArgumentResolver?
Thank you!!!!!
I can't see any simple and elegant solution. Maybe because the problem is not how to data bind abstract classes in Spring MVC, but rather : why having abstract classes in form objects in the first place ? I think you shouldn't.
An object sent from the form to the controller is called a "form (backing) object" for a reason : the object attributes should reflect the form fields. If your form has username and password fields, then you should have username and password attributes in your class.
So credentials should have a UserPasswordCredentials type. This would skip your "abstract instantiation attempt" error. Two solutions for this :
Recommended : you change the type of UserAccount.credentials from Credentials to UserPasswordCredentials. I mean, what Credentials could a UserAccount possibly have, except a UserPasswordCredentials ? What's more, I bet your database userAccounts have a username and password stored as credentials, so you could as well have a UserPasswordCredentials type directly in UserAccount. Finally, Spring recommends using "existing business objects as command or form objects" (see doc), so modifying UserAccount would be the way to go.
Not recommended : you keep UserAccount as is, and you create a UserAccountForm class. This class would have the same attributes as UserAccount, except that UserAccountForm.credentials has a UserPasswordCredentials type. Then when listing/saving, a class (UserAccountService for example) does the conversion. This solution involves some code duplication, so only use it if you have a good reason (legacy entities you cannot change, etc.).
I'm not sure, but you should be using ViewModel classes on your controllers instead of Domain Objects. Then, inside your saveAccount method you would validate this ViewModel and if everything goes right, you map it into your Domain Model and persist it.
By doing so, you have another advantage. If you add any other property to your domain UserAccount class, e.g: private bool isAdmin. If your web user send you a POST parameter with isAdmin=true that would be bind to user Domain Class and persisted.
Well, this is the way I'd do:
public class NewUserAccount {
private String name;
private String username;
private String password;
}
#RequestMapping(value = "/saveAccount", method = RequestMethod.POST)
public #ResponseBody Long saveAccount(#Valid NewUserAccount account)
{
//...
}

Categories