My spring mvc session was changed implicitly after called a service method? - spring

I'm using spring 3.2.5 via annotations and got some issue dealing with session.
My controller class is like this:
#Controller
public class WebController {
#Autowired
private IElementService elementService;
...
//in this method I set the "elementList" in session explicitly
#RequestMapping("/elementSearch.do")
public String elementSearch(
#RequestParam("keyword") String keyword,
HttpSession session){
List<Element> elementList= elementService.searchElement(keyword);
session.setAttribute("elementList", elementList);
return "searchResult";
}
//here I got my problem
#RequestMapping(value="/anotherMethod.do", produces="text/html; charset=utf-8")
#ResponseBody
public String anotherMethod(
...
//I called my service method here like
Element e = elementService.searchElement("something").get(0);
...
}
And I have a ElementServiceImpl class like this:
#Service
public class ElementServiceImpl implements IElementService {
#Autowired
private IBaseDAO baseDao;
#Override
public List<Metadata> searchElement(String keyword) {
List<Metadata> re = baseDao.searchElement(keyword);
return re;
}
}
And I have a BaseDAOImpl class implemented IBaseDAO and annonated with #Repository:
#Repository
public class BaseDAOImpl implements IBaseDAO {
...
}
Here is the problem, when I visit ".../anotherMethod.do", which will call the anotherMethod up there, my "elementList" in session was changed!
Then I looked into the anotherMethod() and found everytime
Element e = elementService.searchElement("something").get(0);
was called, my elementList was change to the new result returned by searchElement method(which returns a List).
But I didn't set session in that method, and I'm not using #SessionAttributes, so I don't understand how could my session attribute changed after calling a service method?
This problem is torturing me right now so any advise would be a great help, thanks!
update: I tried to print all my session attributes around that method call like this:
StringBuilder ss1 = new StringBuilder("-------------current session-------------\n");
session.setAttribute("test1", "test value 1");
log.info("sessionTest - key:test1 value:" + session.getAttribute("test"));
Enumeration<String> attrs1 = session.getAttributeNames();
while(attrs1.hasMoreElements()){
String key = attrs1.nextElement();
ss1.append(key).append(":").append(session.getAttribute(key)).append("\n");
}
log.info(ss1);
But I didn't see whether the "elementList" or the test value which I added just before print. And I do can get some value by
List<Element> elementList = (List<Element>) session.getAttribute("elementList");
and the elementList I get changed after calling service method, just like I posted before. Where my elementList stored? not in the session?
My goal is to show the elementList to the users in a table, and let them pick one of them, then I get the row number of the table and take it as a index of the elemntList, so I'll know which elemnt object the user picked. Are there any better way to do this so I can get rid of that problem?
Thanks again.

Related

How can I use a local variable in the annotation #Preauthorize?

i need to do something like this
String myVar = "myString";
...
#Preauthorize("customMethod(myVar)")
public void myMethod() {
...
}
but I'm failing at it. How can I do that? It says it cannot be resolved
EDIT:I'm decoupling few rest services and sometimes I have to share infos between them
#Value("${my-properties}")
String urlIWantToShare;
...
#PreAuthorize("isValid(#myValue,urlIWantToShare)")
#RequestMapping(value = "**/letsCheckSecurityConfig", method = RequestMethod.GET)
public boolean letsCheckSecurityConfig(#RequestHeader(name = "MY-VALUE") String myValue)) {
return true;
}
this "isValid" custom security method will call an external service, that doesn't know anything about the caller and his infos. I need to transmit few infos and I need to take them from different kind of sources
One of the sources is my application.properties
EDIT2: I managed to do this
#PreAuthorize("isValid(#myValue, #myProperty)")
#RequestMapping(value = "**/letsCheckSecurityConfig", method = RequestMethod.GET)
public boolean letsCheckSecurityConfig(#RequestHeader(name = "MY-VALUE") String myValue,
#Value("${my-property-from-app-properties}") String myProperty))
..but I want to use not only actual static properties but runtime one. Any help?
You can create a wrapper method without parameters which will call the desired method with parameters. In the annotation you can use the method without parameters
Apologies if I have misunderstood what you are trying to do, but from my understanding you're trying to set an annotation at runtime based on a variable / app.properties, so that you can then read this variable and then execute your class?
If this is the case, You cannot do this from an annotation alone as annotations cannot read local variables and cannot be set at runtime.
However, one option for you is to have an object which contains the 'values' of interest for you and then read the values from the object.
Something like the below:
PoJo
public class testObject{
#test
private String myVar;
private String myValue;
//Getters and Setters
}
Get Object values
public void getFields (Object obj){
Field fields = obj.getClass().getDeclaredFields();
for (Field f : fields){
test fieldAnnotation = f.getAnnotation(test.Class);
if (fieldAnnotation != null){
f.get(obj);
// Do checks based on this
}
}
}
Main Class
public static void main(String[] args){
//Create object
testObject test = new testObject();
test.setOne("testOne");
test.setTwo("testTwo");
getFields(test);
}
I've pulled this code based on what I had to do to get the fields - but in my case, I did not know the object types I was going to be passed. You are simply using the annotation to 'mark' the fields you want to retrieve and then reading the value from the object.
If you're in a similar situation, then you can see my answer here: initial answer
Let me know if i've misunderstood this and I can try and further clarify my answer.

Can I "inject" values from message resources into model objects before implicit Jackson serialisation?

I have a REST API built with Spring Boot / Spring MVC, using the implicit JSON serialization via Jackson.
Now, just before the implicit serialization, I would like to "inject" some UI texts from message resources into the objects that Jackson converts into JSON. Is there some neat, simple way to do this?
As a much simplified example, below I'd like to set Section title to a user-visible value, based purely based on its SectionType.
(Sure, I could hardcode the UI texts in SectionType, but I'd rather keep them separate, in resource files, because it's cleaner, and they might be localised at some point. And I can't autowire MessageSource in the entities / model objects which are not Spring-managed.)
#Entity
public class Entry {
// persistent fields omitted
#JsonProperty
public List<Sections> getSections() {
// Sections created on-the-fly, based on persistent data
}
}
public class Section {
public SectionType type;
public String title; // user-readable text whose value only depends on type
}
public enum SectionType {
MAIN,
FOO,
BAR;
public String getUiTextKey() {
return String.format("section.%s", name());
}
}
Somewhere in a #RestController:
#RequestMapping(value = "/entry/{id}", method = RequestMethod.GET)
public Entry entry(#PathVariable("id") Long id) {
return service.findEntry(id);
}
UI texts that I'd like to keep separate from code (messages_en.properties):
section.MAIN=Main Section
section.FOO=Proper UI text for the FOO section
section.BAR=This might get localised one day, you know
And what I'd like to do in a Spring-managed service/bean somewhere (using Messages, a very simple helper wrapping a MessageSource):
section.title = messages.get(section.type.getUiTextKey())
Note that if I call entry.getSections() and set the title for each, it will not affect the JSON output, since the Sections are generated on the fly in getSections().
Do I have to go all the way to custom deseriazation, or is there a simpler way to hook into the model objects just before they get serialized by Jackson?
Sorry if the question is unclear; I can try to clarify if needed.
As I said in the comment you can write an Aspect around every controller method that returns Section.
I wrote a simple example. You have to modify it with the message source.
Controller:
#RestController
#RequestMapping("/home")
public class HomeController {
#RequestMapping("/index")
public Person index(){
Person person = new Person();
person.setName("evgeni");
return person;
}
}
Aspect
#Aspect
#Component
public class MyAspect {
#Around("execution(public Person com.example..*Controller.*(..))")//you can play with the pointcut here
public Object addSectionMessage(ProceedingJoinPoint pjp) throws Throwable {
Object retVal = pjp.proceed();
Person p = (Person) retVal; // here cast to your class(Section) instead of Person
p.setAge(26);//modify the object as you wish and return it
return p;
}
}
Since the aspect is also a #Component you can #Autowire in it.

From request object to the database

I have an app with an AngularJS front-end and a Spring MVC back-end. I'm having some trouble with converting/mapping request objects to domain/dto objects.
On one page you can add a new order to the system, the POST payload would look something like this:
{
memo: "This is some extra info for order",
orderLines: [{productId:3, quantity:4}, {productId:2, quantity:5}, {productId:1, quantity:4}],
shippingDate: "2014-10-08T19:16:19.947Z",
warehouseId: 2
}
The Spring MVC controller method looks like this:
#RequestMapping(value = "/order", method = RequestMethod.POST)
public ResponseEntity<Void> addOrder(#RequestBody #Valid OrderRequest orderRequest, UriComponentsBuilder b) throws Exception {
// the magic
}
Where OrderRequest is filled with the values of the POST request, the OrderRequest and OrderLineRequest look like this:
public class OrderRequest {
private Long id;
private Date shippingDate;
private String memo;
private List<OrderLineRequest> orderLines;
private Long warehouseId;
public OrderRequest() {
}
// getters and setters ommitted
}
public class OrderLineRequest {
private Long id;
private String productCode;
private int quantity;
public OrderLineRequest() {
}
}
My question now is, in order to save an Order object with orderService.add(order) I need to construct the Order object based on the values that were sent in the request. Where/how do I do this?
OPTION 1
The OrderRequest class could have a makeOrder() method with just returns an Order object like so:
public Order makeOrder() {
Order order = new Order();
order.setMemo(this.memo);
order.setShippingDate(this.shippingDate);
...
}
Then I'd have to map the OrderLineRequest which could have their own makeOrderLine method:
public OrderLine makeOrderLine() {
OrderLine orderLine = new OrderLine();
orderLine.setQuantity = this.quantity;
...what to do with only the productId?
}
As you can see I can set the quantity but in the request I only received the productId, but in the database I save the productCode, productName as well, so I need that info from the database, but I don't want to make a database call from the Request object...I also don't want to half of the mapping in the request object and the rest of the mapping in the controller where I do have access to the services.
OPTION 2
I can use Dozer to do the mapping for me, but that would mean injecting the services into the Dozer custom converters which seem equally unclean to me...
OPTION 3
I pass the OrderRequest object to the service layer and let the service layer handle it, but my question would remain, how exactly would the service layer convert it, say you have the method addOrder like this:
public void addOrder(OrderRequest orderRequest) {
}
Would you call another service to convert from one to the other as I don't really want this conversion in a business logic method?
Any help would be appreciated
use the #RequestBody to map your jsonObject that is send with the request , to a DTO .
please refer to the following tutorial .
hope that helps .
and please ask if there is something not clear .

Can I use both #Post and #Get on the same method

I would like to use both #Post and #Get on the same method like
#GET
#POST
#Path("{mode}")
public void paymentFinish(#PathParam("mode") String mode, String s) {
logger.debug("Enter PayStatus POST");
logger.debug(mode);
}
Even I write like this, I got error. What I want is whatever get or post to the sameurl, the same method works. Is it possible? Now I separate two methods, one for get and one for post.
Unfortunately, only one should be used in order to avoid Jersey exception.
But you could do something like :
#GET
#Path("{mode}")
public void paymentFinish(#PathParam("mode") String mode, String s) {
commonFunction(mode);
}
#POST
#Path("{mode}")
public void paymentFinishPOST(#PathParam("mode") String mode, String s) {
commonFunction(mode);
}
private void commonFunction(String mode)
{
logger.debug("Enter PayStatus POST");
logger.debug(mode);
}
By doing so, if you want to change inner behavior of your functions, you will only have to change one function.
Note that method name in java for get vs post need to be different.
After searching a lot trying to avoid the solution above, I found nothing....
Then I decided to create a custom annotation so I didn't have to waste time duplicating methods.
Here's the github link: Jersey-Gest
It allows you to create GET and Post Methods on a single Annotation by generating a new class from it.
I hope it helps you the same way it helped me :)
Edit:
If for some reason the above link stops working, here's what I did:
Created a compile-time annotation #RestMethod for class methods.
Created a compile-time annotation #RestClass for classes.
Create an AnnotationProcessor which generates a new class with Jersey's corresponding annotations and for each method creates a GET and a POST method which callsback to the original method annotated with #RestClass.
All methods annotated with #RestMethod must be static and contained within a class annotated with #RestClass.
Example (TestService.java):
#RestClass(path = "/wsdl")
public class TestService
{
#RestMethod(path = "/helloGest")
public static String helloGest()
{
return "Hello Gest!";
}
}
Generates something like (TestServiceImpl.java):
#Path("/wsdl")
#Produces("application/xml")
public class TestServiceImpl
{
#GET
#Path("/helloGest")
#Produces(MediaType.APPLICATION_XML)
public String helloGestGet()
{
return TestService.helloGest();
}
#POST
#Path("/helloGest")
#Consumes(MediaType.WILDCARD)
#Produces(MediaType.APPLICATION_XML)
public String helloGestPost()
{
return TestService.helloGest();
}
}

Strange Spring #SessionAttributes Behavior

I'm using #SessionAttributes on 2 controllers and am experiencing some very strange behavior. My first controller (ViewController) is simply a view controller that displays JSP pages. The other is a controller that handles Ajax requests (AjaxController). I have a session attribute that is simply an object that has a HashMap as a member. The object is a wrapper around the map. The map is populated from the database and put in the session, which displays fine via the ViewController. However, when I do a delete from the map via an ajax request (AjaxController) and refresh the page, ViewController SOMETIMES shows that the element is removed, yet other times the element is still there. Here's code snippets:
ViewController (the homepage simply displays the contents of the map contained by userSettings
#Controller
#SessionAttributes({"userSettings"})
public class ViewController {
#RequestMapping(value="/", method=RequestMethod.GET)
public String home(ModelMap model) {
UserSettings userSettings = (UserSettings) model.get("userSettings");
String userListenersJson = userSettings.toJson(); // for bootsrtapping the js on the front end
return "views/home";
}
}
AjaxController:
#Controller
#SessionAttributes({"userSettings"})
public class AjaxController {
#RequestMapping(value="/users/listeners/{externalId}", method=RequestMethod.DELETE)
public #ResponseBody
AjaxResponse<?> deleteListener(ModelMap model,
#PathVariable long externalId) {
UserSettings userSettings = (UserSettings) model.get("userSettings");
userSettings.removeSetting(externalId);
return new AjaxResponse<String>(null, true);
}
}
Am I using #SessionAttributes wrong here? Why would this work sometimes and not others? I've also tried putting all of the view and ajax functionality in the same controller and experienced the same behavior.
Thanks for any help!
EDIT:
I've refactored my code a bit to use the UserPrincipal via springsecurity. My understanding is that this object is stored in the session. Regardless, I'm seeing exactly the same behavior.
Here's the UserPrincipal constructor that populates the user settings map. I've set breakpoints here to ensure that the correct listenerDBOs are set - they are, every time. This is the only time the listeners get set from the db into the UserSettings object in CustomUserPrincipal. All other adds/removes are done via the controllers (quick aside: adds never fail... only removes):
public CustomUserPrincipal(UserDBO userDBO) {
// set UserSettings obj
UserSettingsAdapter.addListeners(userDBO.getUserListenerDBOs(), userSettings);
}
The UserSettings object itself:
public class UserSettings implements Serializable {
private static final long serialVersionUID = -1882864351438544088L;
private static final Logger log = Logger.getLogger(UserSettings.class);
private Map<Long, Listener> userListeners = Collections.synchronizedMap(new HashMap<Long, Listener>(1));
// get the listeners as an arraylist
public List<Listener> userListeners() {
return new ArrayList<Listener>(userListeners.values());
}
public Map<Long, Listener> getUserListeners() {
return userListeners;
}
public Listener addListener(Listener listener) {
userListeners.put(listener.getId(), listener);
return listener;
}
// I'm logging here to try and debug the issue. I do see the success
// message each time this function is called
public Listener removeListener(Long id) {
Listener l = userListeners.remove(id);
if (l == null) {
log.info("failed to remove listener with id " + id);
} else {
log.info("successfully removed listener with id " + id);
}
log.info("Resulting map: " + userListeners.toString());
log.info("Map hashcode: " + userListeners.hashCode());
return l;
}
public Listener getListener(long id) {
return userListeners.get(id);
}
}
This is the helper function in the UserSettingsAdapter class that adds to the UserSettings object, called from CustomUserDetails constructor:
public static void addListeners(Set<UserListenerDBO> userListeners, UserSettings userSettings) {
for (UserListenerDBO userListenerDBO : userListeners) {
if (userListenerDBO.isActive()) {
addListener(userListenerDBO, userSettings);
}
}
}
I've also changed the controller code to user the CustomUserPrincipal object instead of #SessionAttributes:
In ViewController:
#RequestMapping(value="/", method=RequestMethod.GET)
public String home(ModelMap model) {
CustomUserPrincipal userPrincipal = authenticationHelpers.getUserPrincipal();
UserSettings userSettings = userPrincipal.getUserSettings();
String userListenersJson = userSettings.toJson();
return "views/home";
}
In AjaxController:
#RequestMapping(value="/users/listeners/{externalId}", method=RequestMethod.DELETE)
public #ResponseBody
AjaxResponse<?> deleteListener(ModelMap model,
#PathVariable long externalId) {
CustomUserPrincipal userPrincipal = authenticationHelpers.getUserPrincipal();
UserSettings userSettings = userPrincipal.getUserSettings();
userSettings.removeListener(externalId);
return new AjaxResponse<String>(null, true);
}
I hope this helps shed some light on the issue!
I ran into a similar problem with #SessionAttributes. A controller had a #SessionAttributes annotation at the class level, and one of the methods handled POST requests, and included an instance of the session-managed object as an argument. This instance was saved to the database, but was re-used by subsequent requests, causing some data corruption. We had to add another method argument of type SessionStatus, and call SessionStatus.setComplete(). This caused the instance to be removed from the session, and prevented reuse and corruption. So try adding a SessionStatus instance to your controllers' handler methods, and invoke setComplete() where appropriate.
EDIT: I accidentally referenced the getter isComplete() in my initial answer; I meant to reference the setter setComplete().
#SessionAttributes is specific to a Controller and is not shared among several Controllers.
Instead, consider using manually session.setAttribute (class HttpSession).
You should have a look here : http://beholdtheapocalypse.blogspot.fr/2013/01/spring-mvc-framework-sessionattributes.html

Resources