Initialize a Struts 1 ActionForm with Data from HttpSession Object - struts

I've done this a half dozen times so I know it's possible. I just can't remember how.
I would like to initialize a property of a Struts 1 ActionForm with data from the user's HttpSession object, but only when the form is first created. Actually don't worry too much about the fact that it comes from HttpSession, important is just the fact that the data is dynamic, per-user, and should only be initialized once.
Additionally, if the user changes the data in this field, the user's entry should persist. In other words, when the user first sees the form they will see the initialized data. If they then change the field and submit the form (by calling the associated action) and subsequently come back to this form later, they should see THEIR entry in that field.
Obviously initializing the field in struts-config.xml won't work because the data is dynamic and per-user. Same can be said for the form's constructor. I see the reset() method of ActionForm will be called to reset properties to a default state, but I don't remember if it is called before the first time the form is loaded and displayed in the page. I suppose if it is that's an option, but I would only want it to do the initialization on the first call. That sounds just mildly complicated (I would need a boFirstTime member variable flag or something?).
Can anyone help?

What I ended up doing was overriding reset() of ActionForm, and setting the desired property only if it is null or blank. The property I needed to initialize is represented in the class member variable _strMailTo (yeah I know nobody but me uses the underscores for member variables anymore).
It turns out that reset() is also called before the ActionForm properties are used for the first time to populate the fields of the form for the associated Action. In this way the first time the user sees the form the my pre-populated data is there. But if they change it and later land on the form again they see the text they put in the field the last time they submitted the form.
I guess maybe I'm also the only one still using Struts 1 anymore...
public void reset(ActionMapping mapping, HttpServletRequest request) {
if (_strMailTo == null || _strMailTo.equals("")) {
String strRemoteUser = request.getRemoteUser();
_strMailTo = chOps.UtilityUsers.getEmail(strRemoteUser);
}
}

Related

Creating an attribute against WHEREUSED Non Persistent Object - Maximo

I have added an attribute against the NP WHEREUSED object.
I'm adding a New Row and the field is getting populated using a LKP and on Save Maximo gets rid of the value.
How can I ensure to maintain the value of the spare fieldon save?
Thanks,
Keepgv
First, you need to understand that it takes code to move anything from a non-persistent (NP) object or attribute to somewhere where it will get saved. Second, you need to understand that the save() method is never called on NP objects; execute() is called instead.
I think the problem you are running into is that Maximo does not currently (as of Maximo 7.6.0.8) offer "Execute" in the selection of events for Object Launch Points. If you would like to see that changed, please vote for this RFE: https://www.ibm.com/developerworks/rfe/execute?use_case=viewRfe&CR_ID=77559
Then, you'll have to find a workaround until IBM implements that RFE.

Best way to persist an ID String?

I am curious as what the best way to persist a user ID is in VBA/Access. The user ID would be used in various forms to track changes a user has made. In .net what I would do is to create a class that holds the user ID variable, or use my.system.settings. Seeing as this is not an option, what are some ways or "the way" I can keep and hold a variable in memory or as a reference. The thought that comes to mind is a "Settings" table, but im not sure if thats overkill, or if there is a known and better method.
Persisting a value means, in Access, storing it in a database table. From your description I assume you mean persisting the value during the current session. In which case, once the value is obtained from a table, you could store it in a global variable, or as a property of an instance of a class that you have instantiated.
Another, Access specific, option is to set the value as the Caption for a hidden label on a form. This can either be on a main switchboard-form, that remains open throughout the session, or on a hidden form that is opened on start-up and, again, remains open throughout the session.
It is possible to create a registry-entry, but I would probably prefer to use a hidden-form. Whichever approach you take, it is important to ensure that the value remains intact throughout the session.
If you want the ID to persist only across that specific session, then a Global variable would probably suit you best.
If you want it to persist on a machine until changed, then using the registry would be better.
'SaveSetting appname, section, key, setting
SaveSetting "MyApp", "ID", "Value", "12345678"
'GetSetting(appname, section, key[, default])
CurrentID=GetSetting "MyApp","ID","Value" ' Add ,"DefaultValue" if you want a marker for if there is no value set
'DeleteSetting appname, section[, key]
DeleteSetting "MyApp", "ID" ' add ,"Value" if you only want this specific key deleted

Why does NHibernate pass default values to an insert if Save is called before the object is populated?

If I call Save on a new object, then populate its properties, NHibernate generates and insert statement that contains only the default values for the properties. For example (session is an open ISession):
var homer = new Person();
session.Save(homer);
homer.Name = "Homer J. Simpson";
session.Flush();
I thought that calling Save would make homer persistent and that NH would track any changes and include them in the insert. Instead, it issues an insert with the name property parameter set to null. If I put the Save call after the assignment then it works. This object has a GUID id assigned by NH so it's not doing a premature insert to get an identity.
ETA I'm using session-per-request in an ASP.NET app and the pattern I want to follow is:
MyObject myObject;
if (id == null)
{
myObject = new MyObject();
repository.Add(myObject);
}
else
{
myObject = repository.GetMyObject(id);
}
// populate myObject's properties
// NH magic happens here when the HTTP request ends
I think your assumption in this case is simply incorrect.
Reading the code sample you provided, you could just as well expect NHibernate to insert the object, and then subsequently change the Name and then issue an Update. That, however, would assume that Flush implicitly saves the changed state.
I also wonder why this happens. NH should really wait to insert the object to the database.
Reasons why could do this:
the id, you already said that you are using guids, so this shouldn't be the reason.
there is a query. To ensure that it is performed on actual data, the session is flushed.
there are calculated columns, which need to be read back from the database
there might be other reasons I don't remember.
Is this really the code you are running to reproduce the test?
How does the mapping file look like?
You just mentioned it in the answer to my (perhaps rather naive) comment. You have set session FlushMode to Auto. Change that to Manual and you're more likely to see the behavior you are seeking.
It's still a rather wild guess, simply because so many other properties of your configuration can be at play.

Determine if an entity field changed in NHibernate

I have a call that needs to determine if a field has changed. But calling get using that entities id returns the same entity not the prior version.
Entity e = Dao.Get(id);
//At this point e.Field is X
e.Field = y;
Dao.Save(e);
Entity Dao.Get(Guid id)
{
return Session.Get(id);
}
Entity Dao.Save(Entity e)
{
Entity olde = Session.Get(e.Id);
if (e.Field != olde.Field) <--- e.Field == olde.Field so it does not run.
DoBigMethod(e);
return e;
}
How do I handle this situation without adding an onChange method to the Entity class.
You only know one "version" of the entity: the current one. There is actually only one version of the entity. You have it in memory and you already changed it and forgot the previous state.
Call get to see the previous database state is dangerous. If changes are already flushed (NHibernate flushes before queries for instance), you get your changes. If you open another session, you see changes from other transactions.
Are you only interested in one single field? Then you can cache the old value somewhere.
If this wouldn't work, you need to tell me more about the reason why you need to know the previous value of this field.
EDIT:
Some more ideas:
cache the previous state of the field when you get the object, in DAO.Get
implement this property that it sets a flag if it changed.
consider to make this change an explicit operation called by the client, instead of an implicit operation that is called when the flag changes. For instance, if this flag is called "Activated", implement a "Activate" and "Deactivate" method. This methods change that flag and perform the "large set of code". The flag is read-only for the rest of the world.

Databinding Silverlight Comboboxes with Lists of Objects - working but ugly

I'm developing a business application, using Silverlight for the UI and a WCF webservice for the back-end. In the database I have a number of lookup tables. When the WCF service returns a business object, one of the properties contains the entire row out of the lookup table instead of just the foreign key, so in the UI I can display things like the description from the lookup table without making another call to the service. What I am trying to do at the moment is provide a combobox bound to the entire list of lookup values and have it update properly. The business object I'm dealing with in this example is called Session and the lookup is called SessionType.
Below is the definition of the combobox. The DataContext is set to an instance of Session. I am setting an ItemTemplate because the combobox is displaying more than just a list of strings.
<ComboBox
x:Name="SessionTypesComboBox"
ItemTemplate="{StaticResource SessionTypeDataTemplate}"
ItemsSource="{Binding Source={StaticResource AllSessionTypes}}"
SelectedItem="{Binding Path=SessionType, Mode=TwoWay}"
/>
Both the business object and the lookup table are being loaded asynchronously via the web service. If I do nothing else, the combobox list will be populated with SessionTypes, but it will not show the initial SessionType value from Session. However Session will be updated with the correct SessionType if the combobox selection is changed.
What seems to be happening is that the SelectedItem binding cant match the SessionType in Session to its equivalent in the SessionType list. The object values are the same but the references are not.
The workaround I have found is to load the Session and the SessionTypes list, then update the current SessionType of Session with the corresponding one from the SesstionTypes list. If I do that then the combobox displays correctly. However to me this has a bad code smell. Because everything is loaded asyncronously, I have to determine when everything is available. Here's how I'm doing that:
In the code-behind of my Silverlight user control:
// incremented every time we get data back during initial form load.
private volatile int m_LoadSequence = 0;
...
// Loaded event, called when the form is er... loaded.
private void UserControl_Loaded(object sender, RoutedEventArgs e)
{
// load session types
var sessionTypes = this.Resources["AllSessionTypes"] as Lookups.AllSessionTypes;
if (sessionTypes != null)
{
sessionTypes.DataLoadCompleted += (s, ea) =>
{
IncrementLoadSequence();
};
sessionTypes.LoadAsync();
}
// start loading another lookup table, same as above
// omitted for clarity
// set our DataContect to our business object (passed in when form was created)
this.LayoutRoot.DataContext = this.m_Session;
IncrementLoadSequence();
}
// This is the smelly part. This gets called by OnBlahCompleted events as web service calls return.
private void IncrementLoadSequence()
{
// check to see if we're expecting any more service calls to complete.
if (++m_LoadSequence < 3)
return;
// set lookup values on m_Session to the correct one in SessionType list.
// Get SessionType list from page resources
var sessionTypes = this.Resources["AllSessionTypes"] as Lookups.AllSessionTypes;
// Find the matching SessionType based on ID
this.m_Session.SessionType = sessionTypes.Where((st) => { return st.SessionTypeID == this.m_Session.SessionType.SessionTypeID; }).First();
// (other lookup table omitted for clarity)
}
So basically I have a counter that gets incremented each time I get data back from the webservice. Since I'm expecting 3 things (core business object + 2 lookup tables), when that counter gets to 3 I match up the references.
To me, this seems very hacky. I would rather see the combobox specify a ValueMemberPath and SelectedValue to match the selected item with one in the list.
Can anyone see a cleaner way of doing this? This situation is very common in business apps, so I'm sure there must be a nice way of doing it.
Geoff,
To confirm I understand your problem: the databinding infrastructure doesn't seem to recognise that two objects you consider 'equal' are actually equal - therefore the initial SelectedItem isn't set correctly since the databinding doesn't find a reference-equals object in your StaticResource collection to match Session.SessionType.
You get around this by 'flattening' the references (ie. you force the Session.SessionType to be reference-equals in the Where((st)...First() code.
We have had a similar problem.
It does kinda of make sense that Silverlight won't automatically 'equate' two objects from difference 'sources' just because you know they represent the same data. Like you said "The object values are the same but the references are not". But how can you MAKE the databinding equate them?
Things we thought of/tried:
implementing .Equals() on the class (SessionType in your case)
implementing operator == on the class (SessionType in your case)
implementing IEquatable on the class (SessionType in your case)
making the collection only Strings and binding to a string property
but in the end we have given up and used the same approach as you - 'extracting' the correct reference-equals object from the 'collection' (after everything is loaded) and poking it into the SelectedItem-bound property.
I agree with you about the code-smell, and suspect there must be a better solution. So far all our debugging in the property accessors and no-op IValueConverters hasn't found a solution -- but if we do I'll post it back here too.
I'm not sure I'm fully understanding the problem (it's early :)) But can't you just transfer all the items you need in one call? (even if you have to wrap the 3 in a new DTO class), then you can just update the current session type using a complete event. It's still not perfect, but at least you don't have to keep any counters.
I'd also move all that logic to a ViewModel and just bind to that, but that's just me :)
You'd be better off binding to an ObservableCollection then using some other code (a View Model part of a MVVM isn't a bad choice) to update it in the background. That way you get separation from the UI and its a lot easier to handle the updates as the UI is just bound.
Thanks for the answers, all of the above were useful! I'm moving towards the MVVM way as well as combining several service calls into a single one (also reduces round-trip overhead). Looks like I'll stick with the lookup re-referencing for the time being - if I find a better way I'll post it as well.
geofftnz,
Have you find any nice solution for this?
CraigD,
I doubt that overriding Equals etc is a good solution. First, this is to be done inside the generated proxy class SessionType, so these changes will be lost on each service reference update. Second, notification in the SessionType setter (here SessionType is the same generated client proxy class) uses ReferenceEquals call... so that's one more place to touch the generated code! OK, the first thing can be done via handmade partial class SessionType (and so will not be lost after updates), but the second thing certainly cannot be done the same way.