-
1. Re: Conversation initial state
jacob.orshalick Jun 24, 2008 5:24 PM (in response to obfuscator)Another option is to pass the selected image to the conversation context through outjection (or programmatically) and use a commandLink. Note that with this approach if you want this entity to be managed in the conversation an EM merge will be required. Also note that you will lose bookmarkable URLs by relying on POST data here. Then again, you state that the objects may not be available from a central storage so these drawbacks may not be an issue.
For example,
@DataModelSelection private Image selectedImage; @Out(scope=ScopeType.CONVERSATION, required=false) private Image image; ... ... public String selectImage() { image = selectedImage; return "detailedImageView"; }
Once the conversation is promoted to long-running, the image will remain in the context across requests. Hope it helps.
-
2. Re: Conversation initial state
obfuscator Jun 25, 2008 11:52 AM (in response to obfuscator)Thank you for that, It helped solve my problem. I think I understand the semantic difference between nested conversations and normal conversations now. If the same example was made using nested conversations, I believe (as hinted by Red Hat Magazine) that in a nested conversation I could simply do
class InConversation { ... @DataModelSelection private Image selectedImage; @Out(required=false) private Image image; ... ... @Begin(nested=true) public String selectImage() { image = selectedImage; return "detailedImageView"; } }
, and the image would be automatically outjected to the newly started nested conversation. This is because the new running scope (nested conversation) and parameter outjection scope determined by the bean scope (conversation) are considered semantically equal (both are variable scope
conversation
). The outjection is therefore determined to end up in the nested one, since the nested conversation was started before the action method call. I think this type of behavior is great for providing initial parameters before, say, a redirect.The example you gave works perfectly, but unlike when using nested conversations, all
initial parameters
need to be explicitly marked to be outjected to the conversation scope (because the outjection scope is not determined by the current scope, but rather (by default) the bean scope), and this time the bean scope is not conversation, it is something different, like EVENT or SESSION, which is considered semantically different to the new running scope (CONVERSATION), and the outjection ends up in the old scope instead.This difference in semantics confused me. In the first case, the variable ends up in the new running scope (by default), and in the second it ends up in the old running scope (by default), with the only difference being that nested conversation scope is considered semantically equal to its lower scope (conversation).
The starting of a nested conversation is therefore very different to normal conversations, and I ask myself if they really need to be? Would it not be useful to simply outject all changed parameters to the new running scope after a scope increase has been encountered (@Begin or equivalent)? This would increase consistency in the framework. There is probably a good reason why not to do so, perhaps this is by design?
Best Regards
Alexander -
3. Re: Conversation initial state
jacob.orshalick Jun 26, 2008 4:39 PM (in response to obfuscator)
The starting of a nested conversation is therefore very different to normal conversations, and I ask myself if they really need to be?Not really. The relationship between a nested conversation and its parent is similar to the relationship between a conversation and the session. Simply allows state to be segregated when alternate flows are necessary within a conversation. If you would like a deep understanding of the conversation model and the reasoning behind its design, it is explained in-depth in Chapters 7, 8, and 9 of Seam Framework: Experience the Evolution of Java EE.
Hope it helps.
-
4. Re: Conversation initial state
obfuscator Jun 27, 2008 3:28 PM (in response to obfuscator)I haven't read the book that you are referring too, but quite a lot of documentation and source code, and I'm still feeling like I'm missing something in Seam. Namely that there is no way of passing variables without starting conversations. Consider passing state from one EVENT-scoped bean to another, when the user moves from one page to another.
If you use redirects, you can (as you mentioned) outject state to the (temporary) conversation, which then can survive a redirect, and you can inject the state in the second bean. You cannot, however, pass state over a GET request boundry. (page 1 -> GET -> page 2). You can use s:link with an action parameter, but that requires a shared context between pages.
I'm thinking the detail image example here again. Consider an overview page listing all images, and another page showing details of a single image. We want to be able to acheive a
split
in the state management, passing different images to different detail views. We want to use RESTful URLs, so that both the link from the overview to the detail page, as well as the link shown when on the detail page is bookmarkable. We also want to optimize in the case when we are coming from the overview page, through passing the already loaded image. To acheive this, we can then use conditional loading in the detail view;- If the detail page is accessed directly, the entity will be loaded from the db using the supplied page parameter.
- If the entity was already loaded in the overview page, it is passed along to the detail view, which then uses this entity using a em.merge().
According to previous reasoning you currently need a conversation to transfer this state between the requests. In fact, you even need to start the conversation before showing the overview view if you want to use GET. In this example there is no natural end to the conversation, so then this conversation just lay around waiting for timeout. Would it therefore not be useful to be able to pass the image between contexts?
I'm thinking something like:
//Used with overview.xhtml @Name("overview") class Overview { @DataModel List<Image> images; @Factory("images") void loadImages() { images = loadFromDb(); } } //Used with detail.xhtml @Name("detail") class Detail { @In(required=false) Image image; //page parameter Long imageId; void pageLoad() { if (image == null) { image = loadFromDb(imageId); } else { log.debug("Image was passed along"); em.merge(image); } } }
And the overview.xhtml:
<h:dataTable value="images" var="i"> <s:link view="detail.xhtml"> <!-- f:param is not essential, it is simply used to set imageId in the URL to make it bookmarkable --> <f:param name="imageId" value="#{i.id}"/> <!-- s:pass does the magic of passing state between requests--> <s:pass targetScopeName="image" value="#{i}"/> </s:link> </h:dataTable>
Seam could support this by extracting the values from the inital scope before rendering the overview page, store them, give the state transfer a tx number, and then put the state back in the target scope when/if the GET request arrive. Another alternative is to make s:link:s action pass the actual object using the same method as above (instead of just passing the name of the variable).
The URL could be something like
detail.xhtml?imageId=4711&stateTxId=234
Each tx would just be a little
initial context
.This could of course also be supported in the same manner as page parameters, with implicit passing and mappings.
What is won with all this? Well,
- Instead of getting the forking properties of nested conversation, we get function-calling semantics, and the new scope:s lifetime is not determined by its predecessor as in nested conversations. This is really good if you want to start new conversations in the detail views, and get separate timeout. Otherwise each
split
just adds to the amount of state that needs to be held for the user, and no branches can ever be removed until the parent conversation ends. - We do not move all state to each new page, just the relevant details, reducing clutter.
- Opening multiple sub-views is not a problem, which it is if you use the
shared conversation with s:link
approach.
On the negative side,
- All the small tx scopes, the initial contexts, still need to be stored with a timeout. However, using above mentioned approach, this can be rather short and cache-based, since page parameters can cover up for cache evictions.
So we can work around the problem of creating a conversation, make all the links bookmarkable, and still get the benefits of state-passing between contexts.
Perhaps this is all unnecessary, but I fail to see how this type of behaviour could be acheived using the current features of Seam.
Thank you for taking time to read all this.
Best RegardsAlexander
-
5. Re: Conversation initial state
jacob.orshalick Jun 27, 2008 4:35 PM (in response to obfuscator)You're over-complicating things.
If you want bookmarks and you want to be able to pass state, simply use request parameters across a redirect and define a param in your detail page definition:
... ... <page view-id="/imageSearch.xhtml"> <navigation> <redirect view-id="/imageDetail.xhtml"> <param name="imageId" value="#{image.id}"/> </redirect> </navigation> </page> ... ... <page view-id="/imageDetail.xhtml"> <param name="imageId" value="#{imageDetailAction.imageId}" /> <begin-conversation /> ... ... </page>
Perform the outjection still, but use a factory method that will be executed on the detail page only when the image is not available already from the conversation context:
@Name("imageDetailAction") @Scope(ScopeType.CONVERSATION) public class ImageDetailAction { private Long imageId; ... ... @In(required=false) @Out(required=false) private Image image; @Factory("image") public void loadImage() { image = em.find(Image.class, imageId); } ... ...
Now your page is RESTful, can accept state, and will begin the conversation when the page is accessed. Sorry if there are any syntax errors, coding directly into the input box here.
There are many, many ways to do things in Seam (this is a very good thing) so if you're having doubts just continue to ask on the forums. Good luck.
-
6. Re: Conversation initial state
obfuscator Jun 27, 2008 5:02 PM (in response to obfuscator)Yes, I think I am complicating things slightly, but there is a purpose :). Your example gets the job done elegantly, but it does not transfer the actual object from the search page to the detail page, only the id. It makes the link on the detail page bookmarkable, but not the links on the search page.
Taking the hotel booking as an example, the problem I am seeing is that the search page is not really a part of the conversation of booking a hotel, and it shouldn't be. Since it is not, it cannot pass the selected hotel object to the start of the booking hotel process, unless you use a POST and a redirect. It would be nice to be able to do that without using POST, to get the links on the search page bookmarkable as well. The combination
transfer state
anduse GET
cannot be combined. I think this would be nice :)
There are many, many ways to do things in Seam (this is a very good thing) so if you're having doubts just continue to ask on the forums. Good luck.Yep, I am beginning to notice this now. It takes a lot of time to understand the rationale behind everything. Almost like learning a new programming paradigm. I won't argue too much, I would just like someone to tell me that it is either just not implemented or that my idea is dumb :)
Cheers
-
7. Re: Conversation initial state
jacob.orshalick Jun 27, 2008 5:39 PM (in response to obfuscator)
Your example gets the job done elegantly, but it does not transfer the actual object from the search page to the detail page, only the id.Actually it will if you outject the Image prior to the redirect, but as you state it does not allow the search page to be bookmarked.
The combination transfer state and use GET cannot be combined. I think this would be nice :)You can but GET requires that state be defined through the query string. Either the state must then be pulled from the context (through some magical operations) or initialized based on the request parameters (i.e. factory creation). Take a look at the URL when an s:link (which initiates a GET) is trying to set the DataModelSelection. It sends a magic value to determine what value to pull from the DataModel in the context. There has been consideration on removing this feature as it does not make sense for a GET request given that it is a temporary loop variable.
You could take some approach similar to this as your situation is similar, but you are going to have to perform some very messy operations to determine whether to pull from the context or initialize based on the passed param (not recommended). If there is concern about performance when loading an entity from a GET, perhaps you should consider a caching solution rather than trying to work around the GET. Seam provides a very elegant multi-layered caching solution that is quite easy to use.
Hope it helps.
-
8. Re: Conversation initial state
obfuscator Jun 27, 2008 6:01 PM (in response to obfuscator)That sounds reasonable. Now I feel more comfortable about how to decide which method to use. Thank you, and have a nice weekend!