Expand my Community achievements bar.

How to send information from my java sling model to a servlet that is called by the frontend?

Avatar

Level 5

Hi all, what happens is that I have a servlet, what it does is to generate a PDF using html and css, then that pdf is sent to the frontend to download it.

But what I need to do now is to send information from the java sling model of the template to the servlet, to generate a PDF with the information that is inside the template.

 

How can I do that, the only solutions that I have found are only for the servlet that are sent to call from the java sling model, but my servlet is sent to call from the frontend, this is the code that I use:


Frontend Call:

 $("#download-pdf").on("click", function () {

    fetch('/bin/pdfgenerator', {
      method: 'GET',
      headers: {
        'Content-Type': 'application/pdf'
      }
    })
      .then(response => {
        if (response.ok) {
          return response.blob();
        } else {
          throw new Error('Error on PDF');
        }
      })
      .then(blob => {
        const url = URL.createObjectURL(blob);
        const link = document.createElement('a');
        link.href = url;
        link.download = 'example.pdf';
        link.click();
      })
      .catch(error => {
        alert("error");
        console.error('Error:', error);
      });
  });


Servlet PDF:

package com.project.core.servlets;

import java.io.ByteArrayOutputStream;
import java.io.IOException;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import com.itextpdf.html2pdf.HtmlConverter;

@WebServlet("/PDFServlet")
public class PDFServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
// HTML content to be converted to PDF
String htmlContent = "<html><head><style>.my-class { color: red; }</style></head><body><div class=\"my-class\">Hello, World!</div></body></html>";

// Generate PDF from HTML content
try {
ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
HtmlConverter.convertToPdf(htmlContent, outputStream);

// Set content type and headers
response.setContentType("application/pdf");
response.setHeader("Content-Disposition", "attachment; filename=\"output.pdf\"");

// Write PDF to response output stream
response.getOutputStream().write(outputStream.toByteArray());
} catch (IOException e) {
e.printStackTrace();
response.getWriter().println("Error generating PDF: " + e.getMessage());
}
}
}

Topics

Topics help categorize Community content and increase your ability to discover relevant content.

14 Replies

Avatar

Community Advisor

Hi,

 

I'm sorry, but I don't understand your question correctly. Here are some general guidelines you should keep in mind:

  1. You should extract your PDF logic into an OSGi service so you can use your methods either in the Sling Model or the servlet.

  2. The data which is available in the Sling Model can be retrieved in the Sling servlet as long as you know the path of the resource.

  3. With the above clarification, you should not have a scenario where you really would need to pass data from a Sling Model to a servlet.

Hope this helps

 



Esteban Bustamante

Ok, I will summarize the problem, we have the following files:

1) PDFServlet.java: a servlet that generates a pdf and then sends it to the frontend to download the generated pdf, the information that the PDF have are hardcoded values.

2) Frontend-functions.js: A javascript file that is used in the frontend of a template (page) that what it does is to call the pdf servlet when a button of the page is clicked, to download the pdf that was generated in the same servlet.

3) productModel.java: A java sling model with getters and its respective information.

The way it works now is only with point 1 and point 2, that is to say, the frontend calls the servlet and the servlet delivers something to the frontend.

Now what I want to do is to use point 1, point 2 and point 3 together, the pdf that the servlet generates I want to have and use the information of the java sling model values (example title, description, images, etc) instead of the hardcoded values that are on the PDFServlet

Avatar

Community Advisor

Ok, got it.

 

So, you have two options here:

  1. As I said, you don't have to pass the values from the Sling Model to the servlet. The Sling Model gets the values by adapting a resource, and you have access to the same resource from your servlet. So, you could simply get the resource and adapt it to the model, or read the properties from the resource and then use them in your servlet as usual. Check this as reference: https://experienceleaguecommunities.adobe.com/t5/adobe-experience-manager/get-component-resource-in-... 
  2. Another approach is to simply get those values from the Sling Model into the HTML, and then, via JavaScript, you can retrieve those values from the HTML and pass them to the servlet.
//HTL
<sly data-sly-use.model="com.your.Model"></sly>
<div id="myDiv" data-title=${model.title @ context='html'} data-title=${model.desc @ context='html'} >

//JS
var allData = $('#div').data(); 
 $("#download-pdf").on("click", function () {

    fetch('/bin/pdfgenerator', {
      method: 'GET',
      data: allData
      headers: {
        'Content-Type': 'application/pdf'
      }
    })

Hope this helps.



Esteban Bustamante

I had some doubts when I started to do it with solution 1.

I don't get the values in the servlet even if I call the java sling model, in this case there are 2 java files, one is the interface (ProductDetail.java) and the values (ProductDetailJava.impl).

But if I try to call it to the servlet it simply does not detect the values of the model:

Example

ProductDetail pdpInfo = request.getResource().adaptTo(ProductDetail.class);
String name = “”;
try {
name = pdpInfo.getProductServingSuggestion();
} catch (Exception e) {
name = “ERROR”;
}

Or also

ProductDetailImpl pdpInfo = request.getResource().adaptTo(ProductDetailImpl.class);
String name = “”;
try {
name = pdpInfo.getProductServingSuggestion();
} catch (Exception e) {
name = “ERROR”;
}

My other doubt is that, as the template (page) is composed of several htl joined together, is it possible to use the mentioned logia to send to call the model in the servlet?

Many of the htl use the same template, but they are different htl and css layouts.

Avatar

Community Advisor

Hey, I think you're a bit confused. Let me expand on approach 1 with an example:

  1. The Sling model has the values because it reads the properties from a resource, i.e., /content/myresource.

  2. The servlet won't have access to the resource, but you can explicitly look for that resource, like this:

String path = "/content/myResource" //this can be passed as queryParameter from the JS call
Resource resource = request.getResourceResolver().resolve(path);

3.With the resource, you can adapt to the model. This will automatically invoke your model and populate it with the information from the resource

MyModel model = resource.adaptTo(MyModel.class);
//Now you can use the data will be filled into your model
// So you could utilize the methods from the model, for example:
String title = model.getTitle();
String desc = model.getDesc();

 

Hope this helps.



Esteban Bustamante

How can I get the path to the component resource? Do you have an example of what a url would look like?

I searched the internet for the following solution and send it by parameters in the frontend fetch:

var pathModel = document.location.pathname + “.infinity.json”;

And doing that gives me for example the value:

/content/foods/us/en/test-folder/test-product.html.infinity.json

And then I add the code that you gave me before in the java servlet

String example= “”;

try {
String pathModel = request.getParameter(“pathModel”);
Resource resource = request.getResourceResolver().resolve(pathModel);

ProductDetailImpl productPDP = resource.adaptTo(ProductDetailImpl.class);

example = productPDP.getTitle();
} catch (Exception e) {
example = “ERROR”;
}

But still not working, even if I do it with the ProductDetail.java and ProductDetailImpl.java

Avatar

Community Advisor

you need to do some string manipulation to make "/content/foods/us/en/test-folder/test-product.html.infinity.json" to "/content/foods/us/en/test-folder/test-product"
as there is no content path available with .html extension so you might be getting nullpointer exception.

Hope this helps

Umesh Thakur 

Avatar

Level 5

Hello, I tried to do the recommendation you give me but still dont work, this is the call to the servlet code (javascript):

$("#download-or-print").on("click", function () {

    var pathModelo = "";
    try {
      var pathNoFormat = document.location.pathname;
      pathModel = pathNoFormat.replace(".html","");
// In this case the value is: /content/foods/us/en/test-folder/test-product
      console.log(pathModel);
    } catch (error) {
      console.log(error);
    }

    alert("BEGIN");

    var nombre = "TEST NAME";
    var descripcion = "Testing";

    console.log(pathModel);
    fetch('/bin/pdfgenerator' + '?pathModelo=' + pathModelo + '&nombre=' + nombre + '&descripcion=' + descripcion, {
      method: 'GET',
      headers: {
        'Content-Type': 'application/pdf'
      }
    })
      .then(response => {
        if (response.ok) {
          return response.blob();
        } else {
          throw new Error('Error al obtener el PDF');
        }
      })
      .then(blob => {
        const url = URL.createObjectURL(blob);
        const link = document.createElement('a');
        link.href = url;
        link.download = 'example.pdf';
        link.click();
      })
      .catch(error => {
        console.error('Error:', error);
      });

  });

 
And the servlet is modified now with this:

package com.project.core.servlets;

import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.StringReader;

import javax.servlet.Servlet;
import javax.servlet.ServletException;

import org.apache.sling.api.servlets.HttpConstants;
import org.apache.sling.api.servlets.SlingAllMethodsServlet;
import org.apache.sling.api.SlingHttpServletRequest;
import org.apache.sling.api.SlingHttpServletResponse;

import com.itextpdf.text.Document;
import com.itextpdf.text.Paragraph;
import com.itextpdf.text.pdf.PdfWriter;
import com.itextpdf.text.html.simpleparser.HTMLWorker;

import java.awt.Color;

import org.osgi.service.component.annotations.Component;
import org.osgi.framework.Constants;

import org.apache.sling.api.resource.Resource;
import com.project.core.models.ProductDetail;

@Component(service = { Servlet.class }, property = {
        Constants.SERVICE_DESCRIPTION + "=PDF Servlet",
        "sling.servlet.methods=" + HttpConstants.METHOD_GET,
        "sling.servlet.paths=" + "/bin/pdfgenerator"
})
public class PDFServlet extends SlingAllMethodsServlet {

    @Override
    protected void doGet(SlingHttpServletRequest request, SlingHttpServletResponse response)
            throws ServletException, IOException {
 
        response.setContentType("application/pdf");
        response.setHeader("Content-Disposition", "attachment; filename=example.pdf");

        String example = "";

        try {
            String pathModel = request.getParameter("pathModel");
            Resource resource = request.getResourceResolver().resolve(pathModel);

            ProductDetail productPDP = resource.adaptTo(ProductDetail.class);
 
            example = productPDP.getNewProductCode();
        } catch (Exception e) {
            example = "ERROR";
        }

        String nombre = request.getParameter("nombre");
        String descripcion = request.getParameter("descripcion");
 
        try {
            ByteArrayOutputStream baos = new ByteArrayOutputStream();
            Document document = new Document();
            PdfWriter.getInstance(document, baos);
            document.open();
 
            String htmlContent = "<html><head><title>PDF Example: " + nombre
                    + "</title><style>body {font-family: Arial, sans-serif;}</style></head><body><h1>Hello, World! "
                    + descripcion + "</h1><h1> "+ example + "</h1></body></html>";
            HTMLWorker htmlWorker = new HTMLWorker(document);
            htmlWorker.parse(new StringReader(htmlContent));

            document.close();
            response.setContentLength(baos.size());
            response.getOutputStream().write(baos.toByteArray());
        } catch (Exception e) {
            e.printStackTrace();
            response.setStatus(SlingHttpServletResponse.SC_INTERNAL_SERVER_ERROR);
        }
    }
}
I Tried to do it with the ProductDetail.java and ProductDetailImpl.java but I still can get the values from the model

Avatar

Level 5

Sorry I make some miss typed wrong the value PathModel and I write sometime PathModelo, but even If I correct that part still not working

Avatar

Community Advisor

@Aaron_Dempwolff you are confusing the Resource Path with the URL path. The model adaptation will work only if you adapt a Resource (node) that meets the conditions of your Sling Model. If you are doing something like window.location you will get the URL path instead. 

 

For example, if you need to use the TeaserModel from wknd page:

 

URL Path: /content/wknd/us/en/adventures

EstebanBustamante_0-1717421429044.png

Resource Path: /content/wknd/us/en/adventures/jcr:content/root/container/teaser

EstebanBustamante_1-1717421582676.png


So, you would need to use the ResourcePath to use the adaptTo method.

 

In HTL, I think you could use ${resource.path @ context='uri'} to get the resource path.

 

Hope this helps.

 



Esteban Bustamante

Hello, analyzing the solution that you provided, two doubts came to my mind.

1) Once I enter the resource path I could obtain information from the java sling model, but I think, to obtain the information I couldn't use the getter methods of the model, I have to obtain the information in some other way or method?

2) Something that I realized, is that my template (page) is composed of several components at the same time (8 components) to build the page, and when I examine the properties in the crx I see that several values that shows the page of the different components are missing, what can I do in that case to obtain the information of each one of those components (example texts, images, titles, etc) from the page (template)?

For example in the java sling model I have the value Allergens that is one of the components that make the page but is not in the CRX of the page but is showing the value in the page


Aaron_Dempwolff_0-1717506683968.png

 

I discover something debugging, I found that the methods (getters) are getting called but no one of these have values, is this related with that the pages are dynamic?

Because the info of the java sling model is mapped in the page (template)

Avatar

Community Advisor

from Javascript or jquery--->AEM Servlet-->Slingmodel. this will be the call hierarchy.

you will create an object of sling model in the servlet then will get the data from sling model see the point 3 in below blogpost https://aemhelper.blogspot.com/2021/08/common-myths-around-sling-model.html  to have a sling model object in servlet, servlet will generate the PDF with the data then will respond to the fron-end call with generated PDF.

Hope this helps

Umesh Thakur

Avatar

Administrator

@Aaron_Dempwolff Did you find the suggestions from users helpful? Please let us know if you require more information. Otherwise, please mark the answer as correct for posterity. If you have found out solution yourself, please share it with the community.



Kautuk Sahni