Open GoogleCodeExporter opened 9 years ago
I will look into this more soon and update with an estimated level of effort
and perhaps some feedback on some API details. This enhancement will not be
included in the 1.7.4 release, but will instead likely be part of 1.8.0 since
it may result in an API change.
Feel free to submit a patch if you want ;-)
I will probably put together a first stab in the next couple of weeks and ask
for some feedback on what I put together. I would get to it sooner (and I
might still), but between basketball, work, chores, and a girlfriend I'm a
little busy over the next week.
Thanks for the suggestions!
Original comment by reesby...@gmail.com
on 23 Mar 2013 at 2:30
Glad to hear that you consider this suggestion for the next release.
Here is a quick-and-dirty solution, for the ones who don't want to wait :)
I know it's not perfect but it works and should demonstrate the idea.
Some notices:
- It requires the conversation plugin in the version 1.7.3 (which supports
custom beans, see issue #6)
- No API changes are needed: only ConversationInterecptor and
ConversationProcessor are overridden
- It reuses the annotation @ConversationAction, a new conversation will be
started if no exist
- Introduction of the interface ConversaitonPreparable which should initialize
the ConversationFields
- the maxIdleTime is hard coded
public class CustomConversationInterceptor extends ConversationInterceptor
{
@Override
public String intercept(ActionInvocation invocation) throws Exception
{
try
{
ActionContext ctx = invocation.getInvocationContext();
HttpServletRequest request = (HttpServletRequest) ctx.get(ServletActionContext.HTTP_REQUEST);
ConversationContextManager contextManager = contextManagerProvider.getManager(request);
final ConversationAdapter adapter = new StrutsConversationAdapter(invocation, contextManager);
try
{
processor.processConversations(adapter);
invocation.addPreResultListener(new PreResultListener()
{
@Override
public void beforeResult(ActionInvocation invocation, String resultCode)
{
adapter.executePostProcessors();
invocation.getStack().getContext()
.put(StrutsScopeConstants.CONVERSATION_ID_MAP_STACK_KEY, adapter.getViewContext());
}
});
return invocation.invoke();
}
catch (ConversationInitializedException e)
{
// catch the exception, that means a new conversation was created
// call the post processor to persist the conversation fields
adapter.executePostProcessors();
invocation.getStack().getContext()
.put(StrutsScopeConstants.CONVERSATION_ID_MAP_STACK_KEY, adapter.getViewContext());
HttpServletResponse response = (HttpServletResponse) ctx.get(ServletActionContext.HTTP_RESPONSE);
String location = createRedirectLocation(request);
// append the conversation id to the redirect url
String locationWithConversationIds = RedirectUtil.getUrlParamString(location, ConversationAdapter
.getAdapter().getViewContext());
String finalLocation = response.encodeRedirectURL(locationWithConversationIds);
response.sendRedirect(finalLocation);
// matter of taste...
return null;
}
}
catch (ConversationIdException cie)
{
return this.handleIdException(invocation, cie);
}
catch (ConversationException ce)
{
return this.handleUnexpectedException(invocation, ce);
}
finally
{
ConversationAdapter.cleanup();
}
}
/**
* Recreates the URL from the request
*/
private static String createRedirectLocation(HttpServletRequest request)
{
StringBuffer location = HttpUtils.getRequestURL(request); // HttpUtils is deprecated
Map<String, String> paramMap = request.getParameterMap();
if (paramMap.isEmpty())
{
return location.toString();
}
StringBuilder sb = new StringBuilder();
sb.append(location.toString());
sb.append("?");
for (Iterator<Entry<String, String>> iterator = paramMap.entrySet().iterator(); iterator.hasNext();)
{
Entry<String, String> entry = iterator.next();
sb.append(entry.getKey()).append("=").append(entry.getValue());
if (iterator.hasNext())
{
sb.append("&");
}
}
return sb.toString();
}
}
public class CustomConversationProcessor extends
DefaultInjectionConversationProcessor
{
private static final long serialVersionUID = 1L;
public void processConversations(ConversationAdapter conversationAdapter) throws ConversationException
{
Object action = conversationAdapter.getAction();
Collection<ConversationClassConfiguration> actionConversationConfigs = this.configurationProvider
.getConfigurations(action.getClass());
boolean initialized = false;
if (actionConversationConfigs != null)
{
for (ConversationClassConfiguration conversationConfig : actionConversationConfigs)
{
String actionId = conversationAdapter.getActionId();
String conversationName = conversationConfig.getConversationName();
String conversationId = (String) conversationAdapter.getRequestContext().get(conversationName);
if (conversationId == null && conversationConfig.containsAction(actionId))
{
long maxIdleTime = 28800000; // TODO the intermediate member doesn't have an idleTime
ConversationUtil.begin(conversationName, conversationAdapter, maxIdleTime);
// Call prepare method
if (action instanceof ConversationPreparable)
{
((ConversationPreparable) action).prepareConversation();
}
ConversationContext newConversationContext = ConversationUtil.begin(conversationName, conversationAdapter,
maxIdleTime);
conversationId = newConversationContext.getId();
conversationAdapter.addPostProcessor(this, conversationConfig, conversationId);
initialized = true;
}
else
{
processConversation(conversationConfig, conversationAdapter, action);
}
}
}
if (initialized)
{
// I didn't want to change the API, so I throw a runtime exception which is caught by the interceptor
throw new ConversationInitializedException();
}
}
}
public class ConversationInitializedException extends RuntimeException
{
private static final long serialVersionUID = 1L;
}
public interface ConversationPreparable
{
void prepareConversation();
}
Example usage:
@ConversationController
public class ExampleAction extends ActionSupport implements
ModelDriven<ExampleModel>, ConversationPreparable
{
private String name;
@ConversationField
private ExampleModel model;
@ConversationAction
public String execute()
{
return SUCCESS;
}
@ConversationAction
public String add()
{
model.getNames().add(name);
return "refresh";
}
@Override
public void prepareConversation()
{
System.out.println("begin");
model = new ExampleModel();
}
public void setName(String name)
{
this.name = name;
}
@Override
public ExampleModel getModel()
{
return model;
}
}
public class ExampleModel
{
private List<String> names = new LinkedList<String>();
public List<String> getNames()
{
return names;
}
}
example.jsp:
<%@ taglib prefix="s" uri="/struts-tags" %>
<%@ taglib uri="/struts-conversation-tags" prefix="sc"%>
<h1>Example</h1>
<s:iterator value="names">
<s:property value="%{toString()}"/><br/>
</s:iterator>
<sc:form namespace="/example" action="example">
<s:textfield name="name"></s:textfield>
<s:submit method="add" value="Add"></s:submit>
</sc:form>
struts.xml:
<struts>
<!-- need conversation plugin 1.7.3 -->
<constant name="com.google.code.rees.scope.conversation.processing.ConversationProcessor" value="myCustomProcessor" />
<bean name="myCustomProcessor" type="com.google.code.rees.scope.conversation.processing.ConversationProcessor"
class="path.to.CustomConversationProcessor" />
<package name="default" extends="struts-default">
<result-types>
<result-type name="conversationRedirectAction" class="com.google.code.rees.scope.struts2.ConversationActionRedirectResult" />
</result-types>
<interceptors>
<interceptor name="customConversationInteceptor" class="path.to.CustomConversationInterceptor" />
<interceptor-stack name="customStack">
<interceptor-ref name="customConversationInteceptor" />
<interceptor-ref name="defaultStack" />
</interceptor-stack>
</interceptors>
<default-interceptor-ref name="customStack" />
</package>
<package name="example" namespace="/example" extends="default">
<action name="example" class="path.to.ExampleAction">
<!-- post-direct-get after submit -->
<result name="refresh" type="conversationRedirectAction">
<param name="namespace">/example</param>
<param name="actionName">example</param>
</result>
<result name="success">/path/to/example.jsp</result>
</action>
</package>
</struts>
Like I've said before, it's only a proof of concept. I think an API change is
inevitable for a proper implementation.
Take your time, I will support you with feedback :)
Original comment by pepsifa...@googlemail.com
on 24 Mar 2013 at 8:42
Some corrections:
- the createRedirectLocation() method in my example has a bug,
request.getParameterMap() returns a Map<String, String[]> instead of
Map<String, String>
- a typo in the notices: issue #5 must be referred
Original comment by pepsifa...@googlemail.com
on 24 Mar 2013 at 10:33
Just to check back, this is still on the radar. Work should begin this week.
Original comment by reesby...@gmail.com
on 7 Apr 2013 at 5:25
Beginning work on this issue.
Original comment by reesby...@gmail.com
on 20 Apr 2013 at 2:22
I haven't yet settled on a permanent solution to this that sits right with me
from a design perspective. I'm going to go ahead and promote 1.7.4 to staging
tonight without this enhancement included.
Original comment by reesby...@gmail.com
on 21 Apr 2013 at 5:36
Original issue reported on code.google.com by
pepsifa...@googlemail.com
on 21 Mar 2013 at 10:00