Page.setVersioned(false) but still serialized. Why? - wicket

I have single page application (all AJAX) and thus don't need page versioning. So I do setVersioned(false). However, the page is still serialized on each AJAX request and I would like to know why.
The Wicket version I use is: 7.6.0
WicketApplication.java
public class WicketApplication extends WebApplication {
public Class<? extends WebPage> getHomePage() {
return HomePage.class;
}
public void init() {
super.init();
getFrameworkSettings().setSerializer(new MySerizalizer("foo"));
System.out.println("SERIALIZER: " + getFrameworkSettings().getSerializer().getClass().getName());
}
static class MySerizalizer extends org.apache.wicket.serialize.java.JavaSerializer {
public MySerizalizer(String applicationKey) {
super(applicationKey);
}
public byte[] serialize(Object object) {
final byte[] serialize = super.serialize(object);
System.out.println("serialized " + String.valueOf(serialize.length) + " bytes of " + object.getClass().getName());
return serialize;
}
}
}
HomePage.java
public class HomePage extends WebPage {
public HomePage() {
final Label label = new Label("message", new PropertyModel<>(foo, "bar"));
label.setOutputMarkupId(true);
add(label);
label.setVersioned(false);
final AjaxLink<Foo> ajaxLink = new AjaxLink<Foo>("plus1") {
public void onClick(AjaxRequestTarget target) {
foo.inc();
target.add(label);
}
};
ajaxLink.setVersioned(false);
add(ajaxLink);
setVersioned(false);
System.out.println("VERSIONED: " + isVersioned());
}
private Foo foo = new Foo();
class Foo implements Serializable {
int bar = 0;
public String getBar() {
return "bar is " + String.valueOf(bar);
}
public void inc() {
bar += 1;
}
}
}
HomePage.html
<!DOCTYPE html>
<html>
<body>
<h1>home page</h1>
<span wicket:id="message">Message goes here</span><br><br>
<input type="button" wicket:id="plus1" value="add one"></input>
</body>
</html>
Log:
SERIALIZER: com.foobar.WicketApplication$MySerizalizer
VERSIONED: false
serialized 2286 bytes of com.foobar.HomePage
serialized 2499 bytes of com.foobar.HomePage
serialized 2499 bytes of com.foobar.HomePage
serialized 2499 bytes of com.foobar.HomePage
serialized 2499 bytes of com.foobar.HomePage
For each click on the button (org.apache.wicket.ajax.markup.html.AjaxLink) the page gets serialized. Why if it is not versioned?

Only stateless pages are not stored. Page versioning is controlling whether a page instance could have several versions in the store.
Since Wicket 7.4.0 it is possible to make the Ajax components and behaviors stateless too!

For documentation about stateless AJAX see https://ci.apache.org/projects/wicket/guide/7.x/single.html#_stateless_ajax_components_behaviors

Related

Wicket 7 Link/Label error when using inheritance

With Wicket 7, I am working on an app that uses a base page as a template for other pages to extend.
On the base page, I want to have a label and a link that changes depending on whether the user is authenticated or not.
Here's my BasePage.html:
<div wicket:id="chromeMenu">foo</div>
<div>
<h2 wicket:id="userGreeting"></h2>
<h2><span wicket:id="loginLabel"></span> </h2>
</div>
<wicket:child/>
and the BasePage.java:
public BasePage() {
super();
add(new ChromeDropDownMenu("chromeMenu", buildMenu()));
add(new Label("pageTitle", new StringResourceModel("page.title", this, null)));
if(BasicAuthenticatedSession.get().isSignedIn()) {
// Do stuff here
} else {
add(new Label("userGreeting", "Hello Visitor"));
add(new Link("loginLink") {
#Override
public void onClick() {
setResponsePage(LoginPage.class);
}
});
add(new Label("loginLabel","Test"));
}
}
HomePage extends BasePage.
HomePage.html
<wicket:extend/>
HomePage.java
public class HomePage extends BasePage {
private static final long serialVersionUID = 1L;
public HomePage() {
super();
setPageTitle(new StringResourceModel("page.title", this, new Model<Serializable>("Admin")));
add(new Label("version", getApplication().getFrameworkSettings().getVersion()));
}
}
HomePage is the class returned by the Wicket application.
When I try to load HomePage, I get the following error:
Last cause: Unable to find component with id 'loginLabel' in [Link [Component id = loginLink]]
Expected: 'loginLink:loginLabel'.
Found with similar names: 'loginLabel'
It points to the <a><span/></a> structure from BasePage.html as the root of the problem.
I've tried a few ways to work around this, but without success. I thought maybe an add(Link).add(Label) might be needed, but that didn't work either.
Any thoughts out there on what I'm missing?
The error message says it all.
Last cause: Unable to find component with id 'loginLabel' in [Link
[Component id = loginLink]]
Expected: 'loginLink:loginLabel'.
Found with similar names: 'loginLabel'
Wicket is expecting the same component hierarchy in your Java code as you've written in the HTML. In BasePage.html you have:
<h2><span wicket:id="loginLabel"></span> </h2>
In the BasePage.java code you need to add loginLabel as a child of loginLink component.
Link loginLink = new Link("loginLink") {
#Override
public void onClick() {
setResponsePage(LoginPage.class);
}
};
add(loginLink);
loginLink.add(new Label("loginLabel", "Test"));
The problem is at
add(new Link("loginLink") {
#Override
public void onClick() {
setResponsePage(LoginPage.class);
}
});
add(new Label("loginLabel","Test"));
The Link should be the parent of the Label:
link = new Link("loginLink") {
#Override
public void onClick() {
setResponsePage(LoginPage.class);
}
};
link.add(new Label("loginLabel","Test"));
add(link);
Few extra notes:
better use BookmarkablePageLink if setResponsePage() is the only thing you need in onClick()
use AbstractLink#setBody(IModel label) instead of Link+Label

AEM 6.1 Sightly basic form submit and redirect to same page

I am trying to do the following on AEM 6.1:
Develop a simple form (3 input fields)
Process the submitted values,
And redirect to the same page with processed values/result
I am able to submit the values to a servlet, and process them (business logic), and the result to a requestparamter so i can retrieve them on the UI. But i am stuck at these:
Redirecting to the same page
And retrieving the request parameters and display them using Sightly.
Code Snippets:
Servlet
#SlingServlet(
methods = { "POST","GET" },
name="com.tti.tticommons.service.servlets.LeadTimeTrendsServlet",
paths = { "/services/processFormData" }
)
public class TTICommonServlet extends SlingAllMethodsServlet{
...
#Override
protected void doPost(SlingHttpServletRequest request,SlingHttpServletResponse response) throws ServletException,IOException {
String result;
try {
Enumeration<String> parameterNames = request.getParameterNames();
Map<String, String> formParametersMap = new HashMap<String, String>();
while (parameterNames.hasMoreElements()) {
paramName = parameterNames.nextElement();
paramValue = request.getParameter(paramName);
.......
.......
}
request.setAttribute("result",result);
response.sendRedirect("/content/ttii/en/**posttest.html**");
}
}
Can anyone please help on ho to retireve the above "result" in posttest.html using sightly.
After lot of research and several trials, i finally had the code working. I had to pick up related info from several answers in stackoverflow. Thanks to all the authors. Posting my solution here so beneficial for others.
Result Form with response from webservice:
Process flow
Submit form data to Servlet's POST method
In Servlet, get the values entered by the user from the request
Make the necessary webservice calls. Get the response(json)
I added the response-json as a parameter to the request
Using Wrapper, forward to the necessary page
Define a WCMUse class for use with Sightly.
Assign the 'request' to the Use-class and process it there
Use the assigned values from the Use-class to the UI using sightly
Code snippets - HTML
<form name="userRegistrationForm" method="post" action="/services/processFormData">
<input type="hidden" name=":redirect" value="posttest.html" />
<input type="submit" title="Submit" class="btn submit btn-success" value="Submit" tabindex="25" name="bttnAction">
<div data-sly-use.model="${'com.abccommons.service.helpers.PostServiceHelper' # slingreq=request }">
**${model.getRawJson}**
</div>
Code snippets - Servlet
#SlingServlet(
label = "ABC - Common Servlet",
metatype = true,
methods = { "POST" },
name="com.abccommons.service.servlets.ABCPostServlet",
paths = { "/services/processFormData" }
)
public class ABCPostServlet extends SlingAllMethodsServlet{
#Override
protected void doPost(SlingHttpServletRequest request,SlingHttpServletResponse response) throws ServletException,IOException {
log.info("\n\n----- ABCPostServlet POST: ");
String paramName;
String paramValue;
String osgiService="";
try {
Enumeration<String> parameterNames = request.getParameterNames();
Map<String, String> formParametersMap = new HashMap<String, String>();
while (parameterNames.hasMoreElements()) {
paramName = parameterNames.nextElement();
paramValue = request.getParameter(paramName);
if (paramName.equals("osgiService")) {
osgiService = paramValue;
} else if (paramName.equals(":cq_csrf_token")) {
//TODO: don't add to the map
} else if (paramName.equals("bttnAction")) {
//TODO: dont' add to the map
} else {
//log.info("\n---ParamName="+paramName+", value="+paramValue);
formParametersMap.put(paramName, paramValue);
}
}
String parametersInJSON = JSONHelper.toJson(formParametersMap);
log.info("\n\n----------- POST paramters in json="+parametersInJSON);
String json = webServiceHelper.getJSON(osgiService, parametersInJSON, request, response);
log.info("\n\n----------- POST json from web service="+json);
request.setAttribute("jsonResponse",json);
//String redirectPage = request.getParameter(":redirect");
//RequestDispatcher dispatcher = request.getRequestDispatcher("/content/en/"+redirectPage);
RequestDispatcher dispatcher = request.getRequestDispatcher("/content/en/postformtest.html");
GetRequest getRequest = new GetRequest(request);
dispatcher.forward(getRequest, response);
} catch (Exception e) {
log.error("SlingServlet Failed while retrieving resources");
} finally {
//TODO
}
}
/** Wrapper class to always return GET for AEM to process the request/response as GET.
*/
private static class GetRequest extends SlingHttpServletRequestWrapper {
public GetRequest(SlingHttpServletRequest wrappedRequest) {
super(wrappedRequest);
}
#Override
public String getMethod() {
return "GET";
}
}
Code snippets - PostServiceHelper - WCMUSe class
public class PostServiceHelper extends WCMUse {
protected final Logger log = LoggerFactory.getLogger(PostServiceHelper.class);
private SlingHttpServletRequest httpRequest;
private String rawJson;
#Override
public void activate() throws Exception {
log.info("\n\n========= PostServiceHelper.activate():"+get("slingreq", SlingHttpServletRequest.class));
this.httpRequest = get("slingreq", SlingHttpServletRequest.class);
//this.resourceResolver = getResourceResolver();
//log.info("\n\n========= getRequest()="+getRequest());
SlingHttpServletRequest tRequest;
Set<String> keys = new HashSet<String>();
Enumeration<?> attrNames = this.httpRequest.getAttributeNames();
while (attrNames.hasMoreElements()) {
String attr = (String) attrNames.nextElement();
//log.info("\n--- Key="+attr);
if (attr.equals("jsonResponse")) {
this.setRawJson((String)this.httpRequest.getAttribute(attr));
//log.info("\n---rawJson is SET with : "+this.rawJson);
}
}
}
public void setRawJson(String json) {
this.rawJson = json;
}
public String getRawJson() {
return this.rawJson;
}
}
This is actually a rather tricky pattern to achieve in Sling. You may be better served by submitting the form asynchronously and updating your HTML dynamically via JavaScript.
If you do need to submit your form in the manner you specify, then your servlet needs to produce the HTML response. To produce a response made up of a rendering of the page identified by the requested path your servlet will need to dispatch the request to the appropriate rendering mechanism. You can reference Get JSP output within Servlet in AEM for information concerning how that can be accomplished. Upon dispatch your page and its components should have access to the submitted form values as well as the attributes set on the request.

Search form that redirects to results page

Hello i have the following problem.
I have a search page lets call it search.xhtml and you can search for a bar-code. This value is unique so the result is always one or zero objects from the database
<p:panelGrid columns="1" style="margin:20px;">
<h:form>
<p:messages id="messages" globalOnly="true" showDetail="false" />
<p:message for="barcode" />
<p:inputText id="barcode" value="#{searchForm.barCode}"
required="true" requiredMessage="Value needed" />
<p:commandButton value="search"
action="#{searchForm.searchBarcode}" id="search"/>
</h:form>
</p:panelGrid>
This is the backingbean:
#ManagedBean
#ViewScoped
public class SearchForm extends BasePage {
private Long barCode;
#ManagedProperty("#{daoManager}")
public DaoManager daoManager;
public void setDaoManager(DaoManager daoManager) {
this.daoManager = daoManager;
}
public Long getBarCode() {
return barCode;
}
public void setBarCode(Long barCode) {
this.barCode = barCode;
}
public String searchBarcode() {
//request to dao to get the object
DataList<Data> data = daoManager.findbybarcode(barCode);
if (data.size() == 0) {
this.addMessage(FacesMessage.SEVERITY_ERROR,
"Not Found: " + barCode);
return null;
} else {
getFacesContext().getExternalContext().
getRequestMap().put("id", data.getId());
return "details";
}
}
So if i go to my details page which expect the parameter id this isnt send to the detail page.
backing bean details page:
#ManagedBean
#ViewScoped
public class DetailBean extends BasePage implements Serializable {
#PostConstruct
public void init() {
if (id != null) {
//Go on with the stuff
} else {
addMessage(FacesMessage.SEVERITY_ERROR,"Object not found");
}
}
}
What am i doing wrong? And is this wrong use of JSF? I know i can generate a list and the click on the result but thats not what i want. Also i can take the barcode from the first bean and pass it as a parameter but i want the details page only to accept the id from the objects. So is my thinking wrong? Or is there a solution to get it like this?
If I understand correctly, you wish to pass the ID of the barcode to the details page and yes this is possible.
getFacesContext().getExternalContext().getRequestMap().put("id", data.getId());
The following line is putting the ID parameter into the request that the client just sent you, but the navigation action to details will result in a different request. Try this instead:
return "details?faces-redirect=true&id=" + data.getId();
This will return an HTTP GET navigation action with the ID of the barcode passed as a request parameter in the request.

GWT FileUpload - Servlet options and handling response

I am new to GWT and am trying to implement a file upload functionality.
Found some implementation help over the internet and used that as reference.
But have some questions related to that:
The actual upload or writing the contents of file on server(or disk) will be done by a servlet.
Is it necessary that this servlet (say MyFileUploadServlet) extends HttpServlet? OR
I can use RemoteServiceServlet or implement any other interface? If yes, which method do I need to implement/override?
In my servlet, after everything is done, I need to return back the response back to the client.
I think form.addSubmitCompleteHandler() can be used to achieve that. From servlet, I could return text/html (or String type object) and then use SubmitCompleteEvent.getResults() to get the result.
Question is that can I use my custom object instead of String (lets say MyFileUploadResult), populate the results in it and then pass it back to client?
or can I get back JSON object?
Currently, after getting back the response and using SubmitCompleteEvent.getResults(), I am getting some HTML tags added to the actual response such as :
pre> Image upload successfully /pre> .
Is there a way to get rid of that?
Thanks a lot in advance!
Regards,
Ashish
To upload files, I have extended HttpServlet in the past. I used it together with Commons-FileUpload.
I made a general widget for form-based uploads. That was to accommodate uploads for different file types (plain text and Base64). If you just need to upload plain text files, you could combine the following two classes into one.
public class UploadFile extends Composite {
#UiField FormPanel uploadForm;
#UiField FileUpload fileUpload;
#UiField Button uploadButton;
interface Binder extends UiBinder<Widget, UploadFile> {}
public UploadFile() {
initWidget(GWT.<Binder> create(Binder.class).createAndBindUi(this));
fileUpload.setName("fileUpload");
uploadForm.setEncoding(FormPanel.ENCODING_MULTIPART);
uploadForm.setMethod(FormPanel.METHOD_POST);
uploadForm.addSubmitHandler(new SubmitHandler() {
#Override
public void onSubmit(SubmitEvent event) {
if ("".equals(fileUpload.getFilename())) {
Window.alert("No file selected");
event.cancel();
}
}
});
uploadButton.addClickHandler(new ClickHandler() {
#Override
public void onClick(ClickEvent event) {
uploadForm.submit();
}
});
}
public HandlerRegistration addCompletedCallback(
final AsyncCallback<String> callback) {
return uploadForm.addSubmitCompleteHandler(new SubmitCompleteHandler() {
#Override
public void onSubmitComplete(SubmitCompleteEvent event) {
callback.onSuccess(event.getResults());
}
});
}
}
The UiBinder part is pretty straighforward.
<g:HTMLPanel>
<g:HorizontalPanel>
<g:FormPanel ui:field="uploadForm">
<g:FileUpload ui:field="fileUpload"></g:FileUpload>
</g:FormPanel>
<g:Button ui:field="uploadButton">Upload File</g:Button>
</g:HorizontalPanel>
</g:HTMLPanel>
Now you can extend this class for plain text files. Just make sure your web.xml serves the HttpServlet at /textupload.
public class UploadFileAsText extends UploadFile {
public UploadFileAsText() {
uploadForm.setAction(GWT.getModuleBaseURL() + "textupload");
}
}
The servlet for plain text files goes on the server side. It returns the contents of the uploaded file to the client. Make sure to install the jar for FileUpload from Apache Commons somewhere on your classpath.
public class TextFileUploadServiceImpl extends HttpServlet {
private static final long serialVersionUID = 1L;
#Override
protected void doPost(HttpServletRequest request,
HttpServletResponse response) throws ServletException, IOException {
if (! ServletFileUpload.isMultipartContent(request)) {
response.sendError(HttpServletResponse.SC_BAD_REQUEST,
"Not a multipart request");
return;
}
ServletFileUpload upload = new ServletFileUpload(); // from Commons
try {
FileItemIterator iter = upload.getItemIterator(request);
if (iter.hasNext()) {
FileItemStream fileItem = iter.next();
// String name = fileItem.getFieldName(); // file name, if you need it
ServletOutputStream out = response.getOutputStream();
response.setBufferSize(32768);
int bufSize = response.getBufferSize();
byte[] buffer = new byte[bufSize];
InputStream in = fileItem.openStream();
BufferedInputStream bis = new BufferedInputStream(in, bufSize);
long length = 0;
int bytes;
while ((bytes = bis.read(buffer, 0, bufSize)) >= 0) {
out.write(buffer, 0, bytes);
length += bytes;
}
response.setContentType("text/html");
response.setContentLength(
(length > 0 && length <= Integer.MAX_VALUE) ? (int) length : 0);
bis.close();
in.close();
out.flush();
out.close();
}
} catch(Exception caught) {
throw new RuntimeException(caught);
}
}
}
I cannot recall how I got around the <pre></pre> tag problem. You may have to filter the tags on the client. The topic is also addressed here.

Apache wicket: Componenet.replaceWith(component) from Linktree.onNodeSelected()

I have a linktree for navigation over data which shall replace a component in another div.
For this in the tree I have the treestatelistener implementing
public void nodeSelected(Object node) {
log.debug("nodeSelected: " + node+ " class: " + node.getClass());
Component content = new Label("content");
content.replaceWith(new Label("content", "Hello World"));
}
}
}
All I get is the message:
"This method can only be called on a component that has already been
added to its parent."
The Label with Id "content" is added to the page on init, so it is there before. What do I do wrong?
You are creating a new parent Component every time your treenode listener is called, without adding the 'content' label.
What I would suggest is to try something like this -
public MyPage extends WebPage {
private Component parent = new WebMarkupContainer("parent");
public WebPage() {
parent.add(new Label("content", "hi");
}
...
public void nodeSelected(Object node) {
log.debug("nodeSelected: " + node+ " class: " + node.getClass());
parent.replaceWith(new Label("content", "Hello World"));
}
}
}
I always have issues with just calling Component.replaceWith(). Since I am doing this with AJAX, I always need to call a target.add().
My solution is for now to have a class MyTree extend LinkTree and in that class override the newNodeComponent().
Since in my application everything happens on the IndexPage.class and there I just replace components, I added a method handleSelection() to the IndexPage.Class(extends BasePage extends webpage). There I decide, based on the clicked nodeObject, how to replace a component in the IndexPage
Public Class MyTree extends Linktree{
protected Component newNodeComponent(String id, IModel<Object> model) {
#Override
protected void onNodeLinkClicked(Object node, BaseTree tree, AjaxRequestTarget target) {
super.onNodeLinkClicked(node, tree, target);
((IndexPage)getPage()).handleSelection(target, (TreeNode)node);
}
#Override
protected ResourceReference getResourceFolderOpen(Object node) {
return getResourceForNode(node);
}
#Override
protected ResourceReference getResourceFolderClosed(Object node) {
return getResourceForNode(node);
}
#Override
protected ResourceReference getResourceItemLeaf(Object node) {
return super.getResourceItemLeaf(node);
}
#Override
protected ResourceReference getImageResourceReference(BaseTree tree, Object node) {
return getResourceForNode(node);
}
}
}
I hope this is explained propperly, else: questions or comments wanted!