How to recursively call servlet from same servlet - java

I would like to recursively call a servlet from itself until my operation completes. The reason I need to do this is because my hosting service has a hard deadline that must be met for all RPC calls. Therefore, I need to break up the operation in managable chunks and call the servlet recursively for each chunk until the operation completes.
I feel like the following code should work, but it is not calling the servlet when the HttpURLConnection is opened and connected. No errors are being thrown either. Any help would be greatly appreciated.
import java.io.DataOutputStream;
import java.io.IOException;
import java.net.HttpURLConnection;
import java.net.URL;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
#SuppressWarnings("serial")
public class ServletExample extends HttpServlet {
HttpServletRequest request;
public void doGet(HttpServletRequest req, HttpServletResponse resp)
throws ServletException, IOException {
request = req;
String strRequestIndex = request.getParameter("requestIndex");
int intRequestIndex = 0;
if (strRequestIndex != null) {
intRequestIndex = Integer.parseInt(strRequestIndex);
}
if (intRequestIndex < 10) {
try {
callSameServlet(intRequestIndex);
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
private void callSameServlet(int requestIndex) throws Exception {
URL recursiveUrl = new URL(request.getRequestURL().toString()
+ "?requestIndex=" + (requestIndex + 1));
HttpURLConnection connection = (HttpURLConnection) recursiveUrl
.openConnection();
connection.setRequestMethod("POST");
connection.setDoInput(true);
connection.setDoOutput(true);
connection.setUseCaches(false);
connection.connect();
DataOutputStream dos = new DataOutputStream(
connection.getOutputStream());
dos.flush();
dos.close();
}
}

As to the technical problem as exposed in your question: the URLConnection is lazily executed. The URLConnection#getOutputStream() in your code only returns a handle to write the request body. You need to use URLConnection#getInputStream() to actually fire the HTTP request and obtain the response body.
See also:
How to use URLConnection to fire and handle HTTP requests?
There are however more problems in your code:
You're firing a POST request, but there's nowhere a doPost() method in your servlet definied. You need to either fire a normal GET or to implement doPost(). But that'll be obvious by investigating the obtained error response as described in the above link.
Also, your servlet is not threadsafe. You should never declare/assign request/session scoped data as instance variable of the servlet. Read this answer to learn more.
Finally, did you consider using RequestDispatcher#include() instead? That will be handled internally and saves you the cost/overhead of firing HTTP requests.
request.getRequestDispatcher("servleturl").include(request, response);
You can then use request attributes instead of request parameters. The whole functional requirement makes little sense by the way. I'd just use a for or while loop on the code you'd like to execute recursively. But that's a different problem.

Related

Sling Filter for Wrapping a Request Body

Use Case:
We are developing an AEM Closed User Group site where users will need to submit forms which trigger workflows. Since the users are authenticated, part of the workflow payload needs to include the user who initiated the form.
I'm considering using AEM Forms for this, which saves to nodes under /content/usergenerated/content/forms/af/my-site but the user is not mentioned in the payload (only the service user). In this case, there are two service users: workflow-service running the workflow, and fd-service which handled the form processing and initial saving. E.G. the following code called from the workflow step reports 'fd-service'
workItem.getWorkflowData().getMetaDataMap().get("userId", String.class);
To work around this constraint,
Workflow initiated from publish AEM instance: All workflow instances are created using a service user when adaptive forms, interactive communications, or letters are submitted from AEM publish instance. In these cases, the user name of the logged-in user is not captured in the workflow instance data.
I am adding a filter servlet to intercept the initial form submission before the AEM Forms servlet using a request wrapper to modify the request body adding the original userID.
In terms of forms, workflows and launchers.. This is basically the setup I have
https://helpx.adobe.com/aem-forms/6/aem-workflows-submit-process-form.html
I have reviewed the following resources:
How to change servlet request body in java filter?
https://coderanch.com/t/364591/java/read-request-body-filter
https://gitter.im/Adobe-Consulting-Services/acs-aem-commons?at=5b2d59885862c35f47bf3c71
https://helpx.adobe.com/experience-manager/6-4/forms/using/forms-workflow-osgi-handling-user-data.html
Here is the code for my wrapper
import org.apache.sling.api.SlingHttpServletRequest;
import org.apache.sling.api.wrappers.SlingHttpServletRequestWrapper;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.servlet.ServletInputStream;
import java.io.*;
public class FormSubmitRequestWrapper extends SlingHttpServletRequestWrapper {
String requestPayload;
private static final Logger log = LoggerFactory.getLogger(FormSubmitRequestWrapper.class);
public FormSubmitRequestWrapper(SlingHttpServletRequest slingRequest) {
super(slingRequest);
// read the original payload into the requestPayload variable
StringBuilder stringBuilder = new StringBuilder();
BufferedReader bufferedReader = null;
try {
// read the payload into the StringBuilder
InputStream inputStream = slingRequest.getInputStream();
if (inputStream != null) {
bufferedReader = new BufferedReader(new InputStreamReader(inputStream));
char[] charBuffer = new char[128];
int bytesRead = -1;
while ((bytesRead = bufferedReader.read(charBuffer)) > 0) {
stringBuilder.append(charBuffer, 0, bytesRead);
}
} else {
// make an empty string since there is no payload
stringBuilder.append("");
}
} catch (IOException ex) {
log.error("Error reading the request payload", ex);
} finally {
if (bufferedReader != null) {
try {
bufferedReader.close();
} catch (IOException iox) {
log.error("Error closing bufferedReader", iox);
}
}
}
requestPayload = stringBuilder.toString();
}
/**
* Override of the getInputStream() method which returns an InputStream that reads from the
* stored requestPayload string instead of from the request's actual InputStream.
*/
#Override
public ServletInputStream getInputStream ()
throws IOException {
final ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(requestPayload.getBytes());
ServletInputStream inputStream = new ServletInputStream() {
public int read ()
throws IOException {
return byteArrayInputStream.read();
}
};
return inputStream;
}
}
Here is my filter
import org.apache.sling.api.SlingHttpServletRequest;
import org.apache.sling.api.SlingHttpServletResponse;
import org.apache.sling.api.resource.ResourceResolver;
import org.apache.sling.engine.EngineConstants;
import org.osgi.framework.Constants;
import org.osgi.service.component.annotations.Component;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.jcr.Session;
import javax.servlet.*;
import java.io.IOException;
#Component(service = Filter.class,
immediate = true,
property = {
Constants.SERVICE_DESCRIPTION + "=Add the CUG userID to any UGC posts",
EngineConstants.SLING_FILTER_SCOPE + "=" + EngineConstants.FILTER_SCOPE_REQUEST,
Constants.SERVICE_RANKING + ":Integer=3000",
EngineConstants.SLING_FILTER_PATTERN + "=/content/forms/af/my-site.*"
})
public class DecorateUserGeneratedFilter implements Filter {
private static final Logger log = LoggerFactory.getLogger(DecorateUserGeneratedFilter.class);
#Override
public void init(FilterConfig filterConfig) throws ServletException {
}
#Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
final SlingHttpServletResponse slingResponse = (SlingHttpServletResponse ) response;
final SlingHttpServletRequest slingRequest= (SlingHttpServletRequest) request;
FormSubmitRequestWrapper wrappedRequest = new FormSubmitRequestWrapper(slingRequest);
log.info("starting ConfirmAlumniStatus workflow");
log.info(getCurrentUserId(slingRequest));
chain.doFilter(wrappedRequest, slingResponse);
}
#Override
public void destroy() {
}
public String getCurrentUserId(SlingHttpServletRequest request) {
ResourceResolver resolver = request.getResourceResolver();
Session session = resolver.adaptTo(Session.class);
String userId = session.getUserID();
return userId;
}
}
When POST submissions get processed by this filter, I'm getting the error below stating the request body has already been read. So it seems the filter ranking might not be high enough.
25.06.2018 13:11:13.200 ERROR [0:0:0:0:0:0:0:1 [1529946669719] POST /content/forms/af/my-site/request-access/jcr:content/guideContainer.af.internalsubmit.jsp
HTTP/1.1] org.apache.sling.engine.impl.SlingRequestProcessorImpl
service: Uncaught Throwable java.lang.IllegalStateException: Request
Data has already been read at
org.apache.sling.engine.impl.request.RequestData.getInputStream(RequestData.java:669)
at
org.apache.sling.engine.impl.SlingHttpServletRequestImpl.getInputStream(SlingHttpServletRequestImpl.java:292)
at
javax.servlet.ServletRequestWrapper.getInputStream(ServletRequestWrapper.java:136)
at
my.site.servlets.FormSubmitRequestWrapper.(FormSubmitRequestWrapper.java:26)
at
my.site.servlets.DecorateUserGeneratedFilter.doFilter(DecorateUserGeneratedFilter.java:75)
at
org.apache.sling.engine.impl.filter.AbstractSlingFilterChain.doFilter(AbstractSlingFilterChain.java:68)
at
org.apache.sling.engine.impl.filter.AbstractSlingFilterChain.doFilter(AbstractSlingFilterChain.java:73)
at
org.apache.sling.engine.impl.filter.AbstractSlingFilterChain.doFilter(AbstractSlingFilterChain.java:73)
at
com.cognifide.cq.includefilter.DynamicIncludeFilter.doFilter(DynamicIncludeFilter.java:82)
at
org.apache.sling.engine.impl.filter.AbstractSlingFilterChain.doFilter(AbstractSlingFilterChain.java:68)
at
org.apache.sling.engine.impl.debug.RequestProgressTrackerLogFilter.doFilter(RequestProgressTrackerLogFilter.java:10
I don't think the service ranking is working. When I view
http://localhost:4502/system/console/status-slingfilter
my filter is listed as shown. Judging from the other filters listed, I think the leftmost number is the filter ranking. For some reason my filter is ranked 0 even though I set is as service.ranking=700
0 : class my.site.servlets.DecorateUserGeneratedFilter (id:
8402, property: service.ranking=700); called: 0; time: 0ms; time/call:
-1µs
Update: I was able to fix the filter rank, making it 700 still gave the IllegalStateException. Making it 3000 made that problem go away. But when request.getInputStream() is called from my wrapper. It returns null.
What you are trying to do might be the easy route, but might not be future-proof for new AEM releases.
You need total control of how your workflow is triggered!:
Your forms should have a field that contains the workflow path (and maybe other information needed for that workflow)
Create a custom servlet that your forms will post to.
In that servlet process all user posted values (from the form). But especially get a hold of the intended workflow path and trigger it using the workflow API.
This way you don't have to mess with launchers and the workflows are triggered by your users using their user id.
Hope this helps.
Right idea, wrong location.
The short answer is that when you implement the SlingHttpServletRequestWrapper it provides a default handling of method calls to the original SlingHttpServletRequest if you're adding a parameter on the fly what you want to do is to make sure that the methods that are interacting with the parameters are overridden so that you can be sure yours is added. So on initialization, call the original parameter map, copy those items in a new map which includes your own values.
Then over ride any methods that would request those values
getParameter(String)
getParameterMap()
getParameterNames()
getParameterValues(String)
Don't touch the InputStream, that's already been processed to obtain any parameters that are being passed in.
Additionally, that is one of two ways you can handle this type of use case, the other option is to use the SlingPOSTProcessors as documented
https://sling.apache.org/documentation/bundles/manipulating-content-the-slingpostservlet-servlets-post.html
which allows you to detect what is being written to the repository and modify the data to include, like your case, an additional field.
if you are looking for code example :
#SlingFilter(order = 1)
public class MyFilter implements Filter {
#Override
public void init(FilterConfig filterConfig) throws ServletException {
return;
}
#Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse,
FilterChain filterChain) throws IOException, ServletException {
ServletRequest request = servletRequest;
if (servletRequest instanceof SlingHttpServletRequest) {
final SlingHttpServletRequest slingRequest = (SlingHttpServletRequest) servletRequest;
request = new SlingHttpServletRequestWrapper(slingRequest) {
String userId = getCurrentUserId(slingRequest);
};
}
filterChain.doFilter(request, servletResponse);
}
#Override
public void destroy() {
return;
}

Handling POST requests from a remote website/Sigfox with Jetty 9.4.1

I'm new to Jetty and networking with Java so please bear with me and my beginners questions.
What I want to do is process a payload sent from Sigfox in the application/x-www-form-urlencoded format i.e. the data will come as "device={device}&data={data}&time={time}".
I've learnt that I can actually make a Sigfox Callback to a Java app server. Correct me if I'm wrong but I was thinking that a servlet can serve just the same purpose?
This is why I decided to embed Jetty into my Java application (I'm using the latest Jetty 9.4.1).
Following the Embedded Jetty tutorial I've made this program:
import java.io.IOException;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import java.net.InetSocketAddress;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.eclipse.jetty.server.Server;
import org.eclipse.jetty.servlet.ServletHandler;
public class MinimalServlets
{
public static void main( String[] args ) throws Exception
{
// Create a basic jetty server object that will listen on port 9081.
// Note that if you set this to port 0 then a randomly available port
// will be assigned that you can either look in the logs for the port,
// or programmatically obtain it for use in test cases.
Server server = new Server(new InetSocketAddress("MY_IP", 9081));
// The ServletHandler is a dead simple way to create a context handler
// that is backed by an instance of a Servlet.
// This handler then needs to be registered with the Server object.
ServletHandler handler = new ServletHandler();
server.setHandler(handler);
// Passing in the class for the Servlet allows jetty to instantiate an
// instance of that Servlet and mount it on a given context path.
// IMPORTANT:
// This is a raw Servlet, not a Servlet that has been configured
// through a web.xml #WebServlet annotation, or anything similar.
handler.addServletWithMapping(HelloServlet.class, "/*");
// Start things up!
server.start();
// The use of server.join() the will make the current thread join and
// wait until the server is done executing.
// See
// http://docs.oracle.com/javase/7/docs/api/java/lang/Thread.html#join()
server.join();
}
#SuppressWarnings("serial")
public static class HelloServlet extends HttpServlet
{
#Override
protected void doGet( HttpServletRequest request,
HttpServletResponse response ) throws ServletException,
IOException
{
response.setContentType("text/html");
response.setStatus(HttpServletResponse.SC_OK);
response.getWriter().println("<h1>Hello from HelloServlet</h1>");
// System.out.println("\nConnected");
// System.out.println(request.getParameter("data"));
}
#Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException
{
// Get POST parameters
String val = req.getParameter("data");
System.out.println(val); //print value of 'data'
// Write a response
resp.setContentType("text/html");
resp.setCharacterEncoding("UTF-8");
resp.setStatus(HttpServletResponse.SC_OK); //respond with "200"
resp.getWriter().printf("data = %s",val);
}
}
}
However it doesn't receive the payload from Sigfox nor does it respond with "200". On the Sigfox end it says the "connection has timed out":
Screenshot of callback error
From my reading of the Jetty architecture, HttpServlet and ServletHandler is the right way to go? So where am I going wrong with my understanding and code?
Thanks in advance!

how to generate entire response code from http request in java?

I have a code but it is printing only the status, what i want is my code to prnt the entire code "Response" on console.
import java.io.IOException;
import java.net.URL;
import java.net.HttpURLConnection;
public class API{
public static void main(String args[]) throws IOException
{
URL url = new URL("http://testpath");
HttpURLConnection http = (HttpURLConnection)url.openConnection();
int statusCode = http.getResponseCode();
System.out.println(statusCode);
}
}
Your class is named API yet you seem to not have carefully read the API for HttpURLConnection. :[
getResponseMessage() is probably what you want.
If you want access to certain headers you will need to use the properties defined in URLConnection
The following methods are used to access the header fields and the contents after the connection is made to the remote object:
getContent
getHeaderField
getInputStream
getOutputStream
Certain header fields are accessed frequently. The methods:
getContentEncoding
getContentLength
getContentType
getDate
getExpiration
getLastModifed
This must work..
String statusCode = http.getResponseMessage();
System.out.println(statusCode);
Use getResponseMessage() method from HttpURLConnection to get completer response message

use of servlet in javascript

I would like to use a java servlet in javascript. For the moment I use this code in javascript:
var req = new XMLHttpRequest();
req.open("GET", "http://localhost:8080/FPvisualizer/test.java" + "?action=test", true);
req.send(null);
req.onreadystatechange = function() {processRequest()};
function processRequest() {
if (req.readyState == 4) {
if (req.status == 200) {
document.getElementById("target").innerHTML = req.responseText;
}
}
}
which communicates with this java servlet:
import java.io.File;
import java.util.Collections;
import java.util.List;
import java.util.Map;
public class LoadOntology2 extends HttpServlet{
public void doGet (HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
String action = request.getParameter("action");
response.setContentType("text/xml");
response.setHeader("Cache-Control", "no-cache");
response.getWriter().write("<message>valid</message>");
}
}
the req.responseText contains the whole contents of the servlet file (i.e. all the code of that file is displayed on the webpage). Does anybody know what I am doing wrong here?
You are requesting the Java source file itself. You haven't compiled it and installed it on a server configured to execute it for the URL you are using.
I don't have any experience with setting up Java Servlets, but the tutorial at Oracle looks like a good starting point. In particular, the part where it says you need an application server.

Redirect saving POST parameters using HTTP

I have 2 web-pages.
So, 1st page takes some POST parameters, and then process it. I want to redirect this query(with all POST params) to my own 2nd page, if parameter "appId" = "myApp";
In start of 1st page I make next:
if (getParameter("id") == "myApp")
{
request.setHttpHeader("") - ??? WHAT MUST BE HERE? WHICH HEADERS?
}
P.S. I need only HTTP solution, using native (java) methods (like forward and redirect) don't help me.
Thanks.
You have to user RequestDispatcher.forward. Here is an example.
import java.io.IOException;
import javax.servlet.RequestDispatcher;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
public class ForwardServlet extends HttpServlet{
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
String name = request.getParameter("name");
/*
* You can do any processing here.
* We will simply output the value of name parameter on server console.
*
*/
System.out.println(name);
String destination = "/DestinationServlet";
RequestDispatcher rd = getServletContext().getRequestDispatcher(destination);
rd.forward(request, response);
}
}
What you're asking for can't be done with pure HTTP. You can only redirect GETs with HTTP. See this answer to a similar question https://stackoverflow.com/a/1310897/116509

Categories