How to produce rendered output from a Sling POST in AEM? - aem

It seems like Sling expects every form POST to modify the JCR. So the expected/standard behavior would be a POST-redirect-GET, which is fine for most things. However, I need to be able to POST to AEM and then use the data in that POST to create a rendered result. Our use of AEM is stateless and so I don't want to carry the POST'd data in Session in order to utilize it in a subsequent GET.
Some have recommended putting the POST'd data in Browser sessionStorage, but that doesn't have broad enough support to be sufficient.
As far as I can tell there is no way for Sling in AEM to take a POST and produce a rendered result.
Here is a screenshot of what a POST produces in the page/resourceType component and in any Sling included jsp's that happen to be involved in the rendering.
I have tried things like using the "nop" operation.
<input type="hidden" name=":operation" value="nop" />
But either way all servlets think a POST is happening and don't render properly.
There is the option of creating a custom servlet, to handle the POST, but then how do you render the templated output and change the request so that all the components think they are serving a GET?
UPDATED:
Here is a screenshot of the "nop" POST.jsp result.

What you can do is create a POST.jsp file in the appropiate resourceType.
If your POST request go to /content/yourapp/something, which has a resourceType: your/app/example. Then you can create a file /apps/your/app/example/POST.jsp with whatever render you wish. You can even include your default rendering script in the POST.jsp file if you need it to be rendered the same as the GET requests.
The other option is to use a servlet registered for POST requests and internally use the SlingRequestProcessor service. That service allow you to programmatically process a request through Sling. You can use a SlingRequestWrapper to wrap your request and override getMethod() to return "GET". That should process the request as if it was a GET request.

This sounds like a somewhat funky use case, IIUC you are using a large request parameter P to drive the rendering?
Using a custom POST servlet should work, if you use something like
slingRequest.getRequestDispatcher(resource).forward(request, response) where request is a wrapper around the actual request, where request.getMethod() returns GET. You can then store your P data in request attributes.
The SlingHttpServletRequestWrapper class can be used to create such wrappers.

Creating a custom servlet to handle a post could be an idea. After successfull write you could redirect to the modified resource - simple 302.
The other solution that comes to my mind is a custom Filter that would do the same. However, since AEM expects to get 200 instead of 302, it would be good to tell by atrribute or parameter that this POST needs to be redirected. Otherwise some of the AEM UI functionalities could brake. This is a quick example of an idea. You would probably need to write something more sophisticated.
#Component(immediate = true)
#Service
#Properties({
#Property(name = Constants.SERVICE_DESCRIPTION, value = "Desc"),
#Property(name = Constants.SERVICE_VENDOR, value = "Company name"),
#Property(name = Constants.SERVICE_RANKING, intValue = RedirectFilter.RANKING),
#Property(name = "filter.scope", value = "request") })
public class RedirectFilter implements Filter {
public static final int RANKING = -1000; // low ranking
#Override
public void init(final FilterConfig filterConfig) throws ServletException {
}
#Override
public void destroy() {
}
#Override
public void doFilter(final ServletRequest request, final ServletResponse response, final FilterChain chain)
throws IOException, ServletException {
if (request.getParameter("redirect").equals("true")) {
((SlingHttpServletResponse) response).sendRedirect(((SlingHttpServletRequest)request).getRequestURI());
}
}
}

Related

How to access suffixes using HTL/Sightly?

TLDR: Create a new AEM page called "mypage.html". Supply suffixes in the URL. Pass this suffixes to an Sling servlet. The suffixes act as URL parameters.
sample desired URL: http://localhost:4502/mypage.html/john/smith
So I created a servlet (used this guide: http://www.aemcq5tutorials.com/tutorials/sling-servlet-in-aem/) that can read a suffix.
#SuppressWarnings("serial")
#SlingServlet(paths="geometrixx/components/hompepage", selectors="name", extensions="html",methods="GET", metatype=true)
public class StaffProfileServlet extends SlingAllMethodsServlet {
private static final Logger log = LoggerFactory.getLogger(CourseBookmarkServlet.class);
#Override
protected void doGet(final SlingHttpServletRequest request,
final SlingHttpServletResponse response) throws ServletException, IOException {
RequestPathInfo rpi = request.getRequestPathInfo();
String[] suffixes = rpi.getSuffix().split("/");
and it working fine if I access it via http://localhost:4502/content/geometrixx/en.name.html/first/last
What I want to do next is create a new page called "mypage.html" and supply first and last as suffixes.
mypage will display information relevant to the person in a properly formatted page. With code above, all I get is JSON response.
Some assumptions/changes which I think is needed to achieve my goal:
I will be using paths and using request parameters (i.e. using request.getParameter("myparameter") on servlet code
I will be using AJAX to access the servlet
If my assumptions are correct, how do I access suffixes from HTL/Sightly? I understand I can get the URI via ${request.requestURI} or even Javascript. And using this value, I can then use this in my AJAX call.
But is this the AEM/Sling way of doing it? Or perhaps there is a better way to do what I want?
Thanks a lot!
You can use RequestPathInfo interface from HTL to access suffix's. ${request.requestPathInfo.suffix}
Global objects accessible through HTL -> here.
Methods accessible through request object -> here.

How to Pass object to REST Get Method

I am using Jersey Rest implementation. There are one Rest Services Called HelloWorld. See the below code.
Please consider this code as reference not as compiled code.
#Path("helloWorld")
public class HelloWorld{
#Path("test")
#Produces(...)
#Consum(...)
#GET
public Response test(Person person){
System.out.println(person);
}
}
I am using Jersey client to sent the request.
Here My question is apart from POST method is there any way to send the object to GET method directly. Instead of QueryString.
Please let me if there is any way to do so.
Thanks
So the problem shouldn't be with the server. I did a few tests on different servers (not weblogic as I don't use it) and all of them seem to have no problems accepting a body in the GET request. The problem seem to be with the client. To test I used the following code
ClientBuilder.newClient()
.target("http://localhost:8080/api/get-body")
.property(ClientProperties.SUPPRESS_HTTP_COMPLIANCE_VALIDATION, true)
.request()
.method(HttpMethod.GET, Entity.text("Hello World"));
The SUPPRESS_HTTP_COMPLIANCE_VALIDATION allows us to pass a body to the request. If we didn't use this, then we would get an error.
The problem with this code, is that even though we set this override property, the client completely overrides the GET method and automatically makes it a POST method, so I would get back a 405 Method Not Allowed.
The solution I came up with is to just allow the client to set a header, e.g. X-GET-BODY-OVERRIDE, and then use a #PreMatching filter on the server side to check for this header. If the header is present, then just change the method to a GET
#Provider
#PreMatching
public class GetWithBodyFilter implements ContainerRequestFilter {
#Override
public void filter(ContainerRequestContext request) throws IOException {
String getOverride = request.getHeaderString("X-GET-BODY-OVERRIDE");
if (getOverride != null && "true".equalsIgnoreCase(getOverride)) {
request.setMethod(HttpMethod.GET);
}
}
}
Then just register the filter with the server side. On the client, you would simply need to add the header
ClientBuilder.newClient()
.target("http://localhost:8080/api/get-body")
.property(ClientProperties.SUPPRESS_HTTP_COMPLIANCE_VALIDATION, true)
.request()
.header("X-GET-BODY-OVERRIDE", "True")
.method(HttpMethod.GET, Entity.text("Hello World"));
This solution is good because it takes into account more than just the Jersey client, in regards with being able to send a body in the GET request.

AEM - URL does not change with RequestDispatcher forward

When i do a Forward using RequestDispatcher.. the result page loads but the URL does not change.
The URL where we start and Submit data to the PostServlet: http://localhost:4502/content/en/postformtest.html
Final result URL should be: http://localhost:4502/content/en/postformtestresult.html
But is: http://localhost:4502/services/processFormData
What am i missing? Appreciate any thoughts.
Code snippets..
The HTML Form:
<form name="userRegistrationForm" method="post" action="/services/processFormData">
<input type="submit" title="Submit" class="btn submit btn-success" value="Submit" tabindex="25" name="bttnAction">
</form>
The POST Servlet
#SlingServlet(
label = "Common POST Servlet",
metatype = true,
methods = { "POST" },
name="com.commons.service.servlets.TPostServlet",
paths = { "/services/processFormData" }
)
public class TPostServlet extends SlingAllMethodsServlet{
#Override
protected void doPost(SlingHttpServletRequest request,SlingHttpServletResponse response) throws ServletException,IOException {
final SlingHttpServletRequest syntheticRequest = new SyntheticSlingHttpServletGetRequest(request);
final RequestDispatcherOptions options = new RequestDispatcherOptions();
options.setReplaceSelectors("");
options.setForceResourceType("cq/Page");
request.getRequestDispatcher("/content/en/postformtestresult.html", options).forward(syntheticRequest, response);
}
}
The Wrapper Servlet:
public class SyntheticSlingHttpServletGetRequest extends
SlingHttpServletRequestWrapper {
private static final String METHOD_GET = "GET";
public SyntheticSlingHttpServletGetRequest(final SlingHttpServletRequest request) {
super(request);
}
#Override
public String getMethod() {
return METHOD_GET;
}
}
As the javadocs for RequestDispatcher indicate, the RequestDispatcher and by association the forward method act as wrappers around the resource essentially allowing the delegation of further processing to the resource. This is done behind the scenes so to speak and as such the requested URL will not change - it is not a redirect.
Based on the content of your question I presume what you are trying to accomplish is a traditional form POST to a page. This is actually a rather cumbersome pattern to achieve in AEM and you would most likely be better served by submitting the form asynchronously and then redirecting based on the response.
If all you need is a simple redirect after form processing this can be achieved by calling the sendRedirect method of the response.
If, however, you do need to POST to a page which is then going to handle both the form processing and the page rendering, you could employ a method similar to the OOB form components. The OOB com.day.cq.wcm.foundation.forms.impl.FormsHandlingServlet is implemented as both a Servlet and a request level Filter. As a filter it catches to POST request to the page prior to processing, forwards it using the RequestDispatcher to its Servlet nature, and the Servlet in turn is able to process the request and then forward it, again using the RequestDispatcher, to the page after wrapping the request as a GET request similar to what you are doing above. A bit circuitous but, as noted, this is a cumbersome pattern to realize.
Have you looked at ACS Commons Forms? They do support PRG as a standard form handling process. Check it out at http://adobe-consulting-services.github.io/acs-aem-commons/features/forms.html
You may end up using the feature as is or will get some hint for implementation.
The git link for the same is https://github.com/Adobe-Consulting-Services/acs-aem-commons

Contextual serialization from WebApi endpoint based on permissions

I am using the Asp.Net Web Api. I would like to be able to filter out certain fields on the response objects based on the connected clients access rights.
Example:
class Foo
{
[AccessFilter("Uberlord")]
string Wibble { get; set; }
string Wobble { get; set; }
}
When returning data the filed Wibble should only be returned if the current users context can satisfy the value of "Uberlord".
There are three avenues that I am exploring but I have not got a working solution:
A custom WebApi MediaTypeFormatter.
A custom json.net IContractResolver.
Some sort of AOP wrapper for controllers that manipulates the response object
My issue with these are:
The custom formatter does not feel like the right place to do it but might be the only option.
The custom json serializer would not have access to the current context so I would have to work that out.
With the first two options you would require specific implementations for each response format, json, xml, some custom format, etc. This would mean that if another response type is supported then a custom formatter / serializer is required to prevent sensitive data leaking.
The AOP controller wrapper would require a lot of reflection.
An additional bonus would be to strip out values from the fields on an inbound request object using the same mechanism.
Have I missed an obvious hook? Has this been solved by another way?
It was actually a lot simpler than I first thought. What I did not realise is that the DelegatingHandler can be used to manipulate the response as well as the request in the Web Api Pipeline.
Lifecycle of an ASP.NET Web API Message
Delegating Handler
Delegating handlers are an extensibility point in the message pipeline allowing you to massage the Request before passing it on to the rest of the pipeline. The response message on its way back has to pass through the Delegating Handler as well, so any response can also be monitored/filtered/updated at this extensibility point.
Delegating Handlers if required, can bypass the rest of the pipeline too and send back and Http Response themselves.
Example
Here is an example implementation of a DelegatingHandler that can either manipulate the response object or replace it altogether.
public class ResponseDataFilterHandler : DelegatingHandler
{
protected override System.Threading.Tasks.Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
{
return base.SendAsync(request, cancellationToken)
.ContinueWith(task =>
{
var response = task.Result;
//Manipulate content here
var content = response.Content as ObjectContent;
if (content != null && content.Value != null)
{
((SomeObject)content.Value).SomeProperty = null;
}
//Or replace the content
response.Content = new ObjectContent(typeof(object), new object(), new JsonMediaTypeFormatter());
return response;
});
}
}
Microsoft article on how to implement a delegating handler and add it to the pipeline.HTTP Message Handlers in ASP.NET Web API
I have a similar question in the works over here: ASP.NET WebAPI Conditional Serialization based on User Role
A proposed solution that I came up with is to have my ApiController inherit from a BaseApiController which overrides the Initalize function to set the appropriate formatter based on the user's role. I haven't decided if I will go this way yet, but perhaps it will work for you.
protected override void Initialize(System.Web.Http.Controllers.HttpControllerContext controllerContext)
{
base.Initialize(controllerContext);
// If the user is in a sensitive-data access role
controllerContext.Configuration.Formatters.Add(/*My Formatter*/);
// Otherwise use the default ones added in global app_start that defaults to remove sensitive data
}

adding http headers in call to SoapHttpClient service

I have to consume a service provided by one of our partners. I was given little direction, but was told the security was to be PasswordDigest. I looked it up and immediatly saw lots of references to WSE, so off I went. It was very easy to implement and in no time I had a standard WSE user token using PasswordDigest sitting in the SOAP headers of my messages.
When we started testing today I was immediatly told (by the error message) that things weren't right. Turns out, out partner doesn't look in the SOAP header, but rather wants the security info in the http header.
I have seen lots of articles on how to add custom http headers to a proxy class, but my proxy inherits from SoapHttpClientProtocol which doesn't have a headers collection to add to. I was looking at making a raw httpWebRequest, but I have a specific method to access that has some complex parameters to deal with (and besides it feels like going backwords).
What is the best way to add custom http headers to a service proxy class that doesn't have a GetWebRequest method?
For reference:
Proxy class decleration:
[System.CodeDom.Compiler.GeneratedCodeAttribute("System.Web.Services", "2.0.50727.3053")]
[System.Diagnostics.DebuggerStepThroughAttribute()]
[System.ComponentModel.DesignerCategoryAttribute("code")]
[System.Web.Services.WebServiceBindingAttribute(Name="MtomServiceSoap11", namespace="http://ws.xxxxxxx.com/")]
public partial class MtomServiceService : System.Web.Services.Protocols.SoapHttpClientProtocol {
Target method I need to call:
[System.Web.Services.Protocols.SoapDocumentMethodAttribute("", Use=System.Web.Services.Description.SoapBindingUse.Literal, ParameterStyle=System.Web.Services.Protocols.SoapParameterStyle.Bare)]
[return: System.Xml.Serialization.XmlElementAttribute("uploadDocumentResponse", Namespace="http://ws.edsmtom.citizensfla.com/")]
public uploadDocumentResponse uploadDocument([System.Xml.Serialization.XmlElementAttribute(Namespace="http://ws.xxxxxxx.com/")] uploadDocumentRequest uploadDocumentRequest) {
object[] results = this.Invoke("uploadDocument", new object[] {
uploadDocumentRequest});
return ((uploadDocumentResponse)(results[0]));
}
}
The actual call to the Service is simple. The objects being pass in are not:
request.criteria = docCriteria;
request.document = document;
var result = service.uploadDocument(request);
Thanks.
It figures that 30 minutes after posting I would stumble across the answer. While the proxy class decelaration does not create a GetWebRequest method, its base class System.Web.Services.Protocols.SoapHttpClientProtocol has it and it can be overridden.
protected override System.Net.WebRequest GetWebRequest(Uri uri)
{
var request = base.GetWebRequest(uri);
request.Headers.Add("blah", "blah"); // <----
return request;
}