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
Related
I am new to the MVVM architecture in Android, and I have some days with a doubt that I consider basic, but that I can't solve.
I proceed to discuss my problem:
I have an Entity, CustomerView (this entity is created from a DatabaseView):
#DatabaseView("select ... ")
public class CustomerView {
public String cardCode;
public String cardName;
public String cardFName;
...
Then, I have a Dao class:
#Dao
public interface OCRD_DAO {
...
#Query("SELECT * from CustomerView where cardCode= :cardCode")
LiveData<CustomerView> getCustomerViewByCardCode(String cardCode);
...
}
The repository class, makes use of the DAO class:
public LiveData<CustomerView> getCustomer(String cardCode){
return mOcrdDao.getCustomerViewByCardCode(cardCode);
}
The CustomerSheetViewModel class:
public class CustomerSheetViewModel extends BaseObservable {
private Repository mRepository;
public LiveData<CustomerView> mCustomer;
private MutableLiveData<String> _cardName;
#Bindable
public MutableLiveData<String> getCardName(){
return this._cardName;
}
public MutableLiveData<String> setCardName(String value){
// Avoids infinite loops.
if (mCustomer.getValue().cardName != value) {
mCustomer.getValue().cardName = value;
// React to the change.
saveData();
// Notify observers of a new value.
notifyPropertyChanged(BR._cardName);
}
}
public CustomerSheetViewModel (Application application, String cardCode) {
mRepository = new Repository(application);
this.mCustomer = mRepository.getCustomer(cardCode);
//Init MutableLiveData????
this._cardName = this.mCustomer.getValue().cardName;
//Null Exception, because this.mCustomer.getValue() is null
}
}
At this point, my problem occurs: when I initialise the CustomerView object, it is of type LiveData. However, if I want to make use of 2-way binding, I need an object of type MutableLiveData. So, I think I should create the MutableLiveData object with the data extracted from the database (i.e. from the call to the repository). When I try this (e.g. getValue().cardName) a null exception is thrown, since LiveData is asynchronous.
Finally, I could make use of this property in the layout:
android:text="#={customerSheetViewModel.cardName}"
I really appreciate any help, as I can't find any reference to 2-way binding when the data comes from a database read.
Thanks in advance.
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.
Is there any way to check in my controller's method that I got plain HTTP GET/POST but not redirected request from another controller?
For instance I have
#Controller
public class A {
#RequestMapping("page1")
public String loadPage(final Model model) {
{
// SHOULD CHECK HERE WAS IT INVOKED FROM CLASS' B 'loadPage' METHOD
// OR DIRECTLY BY USER
if (!model.containsAttribute("myForm"))
{
model.addAttribute("myForm", new MyForm());
}
return "view1";
}
}
#Controller
public class B {
#RequestMapping("page2")
public String loadPage(final Model model, RedirectAttributes redirectAttributes) {
MyForm myForm = new MyForm();
// ...
// populate form here with some values
// myForm.setEntries(...);
redirectAttributes.addFlashAttribute("myForm", myForm);
return "redirect:page1";
}
}
UPDATE: To prove question actuality please see updated code snippet with flashAttributes and check for existence at Model for the form DTO. As you can see myForm attributes may not be added to Model since it it existed there already - this approach is performed due to ability to show already fulfilled MyForm in case of request on loadPage from Controller B.
I have a Controller configured on Spring, and I have to workout a DB connection through it to call DAO operations.
This connection is actually available in a session variable, which is not accessible at the momment to the Spring Controller due to it is not HttpServlet inherited.
What is the right way to this Controller access the session variables? Must I implement methods doGet and doPost, inherited from HttpServlet, in order to manipulate the request object? Can it rattle Spring controll over the class?
Thanks for responding.
#Controller
public class SpringController {
#RequestMapping("/create")
public String form(MyCar myCar) {
/*That's where I have to retrieve hibernateSession from
* HttpSession and pass to DAO class do its work.
*/
MyCarDAO myCarDao = new MyCarDAO(session);
myCarDao.saveOrUpdate(myCar);
return "WEB-INF/views/projeto/novo.jsp";
}
}
You can add a HttpSession parameter to your method:
#RequestMapping("/create")
public String form(MyCar myCar, HttpSession session) {
...
}
Spring will automatically add the session parameter when the method is called.
Check the documentation of RequestMapping for possible parameters
Suppose that you declare 3 session attributes, but use only 1 of them in your handler method parameters, so:
#SessionAttributes({ "abc", "def", "ghi" })
public class BindingTestController {
#ModelAttribute("abc")
public String createABC() {
return "abc";
}
#RequestMapping(method = RequestMethod.GET)
public void onGet(#ModelAttribute("abc") String something) {
// do nothing :)
}
#RequestMapping(method = RequestMethod.POST)
public void onPost(#ModelAttribute("abc") String something, BindingResult bindingResult, SessionStatus sessionStatus) {
sessionStatus.setComplete();
}
}
There are lots of example if hit it in google
IMO Right way should be to store the connection in a session-scoped bean instead of a session variable.
Use
#Scope(value = "session")
(cf. http://static.springsource.org/spring/docs/3.0.0.M3/reference/html/ch04s04.html)
I am writing a wizard-like controller that handles the management of a single bean across multiple views. I use #SessionAttributes to store the bean, and SessionStatus.setComplete() to terminate the session in the final call. However, if the user abandons the wizard and goes to another part of the application, I need to force Spring to re-create the #ModelAttribute when they return. For example:
#Controller
#SessionAttributes("commandBean")
#RequestMapping(value = "/order")
public class OrderController
{
#RequestMapping("/*", method=RequestMethod.GET)
public String getCustomerForm(#ModelAttribute("commandBean") Order commandBean)
{
return "customerForm";
}
#RequestMapping("/*", method=RequestMethod.GET)
public String saveCustomer(#ModelAttribute("commandBean") Order commandBean, BindingResult result)
{
[ Save the customer data ];
return "redirect:payment";
}
#RequestMapping("/payment", method=RequestMethod.GET)
public String getPaymentForm(#ModelAttribute("commandBean") Order commandBean)
{
return "paymentForm";
}
#RequestMapping("/payment", method=RequestMethod.GET)
public String savePayment(#ModelAttribute("commandBean") Order commandBean, BindingResult result)
{
[ Save the payment data ];
return "redirect:confirmation";
}
#RequestMapping("/confirmation", method=RequestMethod.GET)
public String getConfirmationForm(#ModelAttribute("commandBean") Order commandBean)
{
return "confirmationForm";
}
#RequestMapping("/confirmation", method=RequestMethod.GET)
public String saveOrder(#ModelAttribute("commandBean") Order commandBean, BindingResult result, SessionStatus status)
{
[ Save the payment data ];
status.setComplete();
return "redirect:/order";
}
#ModelAttribute("commandBean")
public Order getOrder()
{
return new Order();
}
}
If a user makes a request to the application that would trigger the "getCustomerForm" method (i.e., http://mysite.com/order), and there's already a "commandBean" session attribute, then "getOrder" is not called. I need to make sure that a new Order object is created in this circumstance. Do I just have to repopulate it manually in getCustomerForm?
Thoughts? Please let me know if I'm not making myself clear.
Yes, sounds like you may have to repopulate it manually in getCustomerForm - if an attribute is part of the #SessionAttributes and present in the session, then like you said #ModelAttribute method is not called on it.
An alternative may be to define a new controller with only getCustomerForm method along with the #ModelAttribute method but without the #SessionAttributes on the type so that you can guarantee that #ModelAttribute method is called, and then continue with the existing #RequestMapped methods in the existing controller.