avatar
Implement itextPDF external library for AEM AEM

> Install dependency for all/pom.xml and core/pom.xml in AEM Project Structure. First and foremost, go to https://mvnrepository.com/artifact/com.itextpdf/itextpdf to get syntax upon using Maven.

Note: The version that we use and the target is 5.5.13. Thus, you can access this version and get the syntax below:

<!-- https://mvnrepository.com/artifact/com.itextpdf/itextpdf -->
<dependency>
    <groupId>com.itextpdf</groupId>
    <artifactId>itextpdf</artifactId>
    <version>5.5.13</version>
</dependency>

> First, we will install itextpdf in all/pom.xml as the highest priority

<embeddeds>
    <embedded>
        <groupId>com.itextpdf</groupId>
        <artifactId>itextpdf</artifactId>
        <target>/apps/flagtick-packages/application/install</target>
    </embedded>
</<embeddeds>

...
<dependencies>
    <dependency>
        <groupId>com.itextpdf</groupId>
        <artifactId>itextpdf</artifactId>
        <version>5.5.13</version>
    </dependency>
</dependencies>

Note: flagtick-packages will be found in filter.xml which follows up the path: all/src/main/content/META-INF/vault/filter.xml.

> In The next step, we will target the file pom.xml located in the directory path: core/pom.xml.
<dependencies>
    <dependency>
        <groupId>com.itextpdf</groupId>
        <artifactId>itextpdf</artifactId>
    </dependency>
</dependencies>

> Implement ResourceResolver which defines the API which may be used to resolve Resource objects and work with such resources like creating, editing, or updating them

ResourceResolverService.java

ResourceResolverService
package com.flagtick.core.services;

import org.apache.sling.api.resource.LoginException;
import org.apache.sling.api.resource.ResourceResolver;

public interface ResourceResolverService {
    ResourceResolver getResourceResolver() throws LoginException;
}

ResourceResolverServiceImpl.java

package com.flagtick.core.services.impl;

import com.flagtick.core.services.ResourceResolverService;
import java.util.Collections;
import org.apache.sling.api.resource.LoginException;
import org.apache.sling.api.resource.ResourceResolver;
import org.apache.sling.api.resource.ResourceResolverFactory;
import org.osgi.framework.Constants;
import org.osgi.service.component.annotations.Component;
import org.osgi.service.component.annotations.Reference;

@Component(
        service = ResourceResolverService.class,
        property = {
                Constants.SERVICE_ID + "= FLAGTICK Resource Resolver Service",
                Constants.SERVICE_DESCRIPTION
                        + "= This service is responsible for returning an instance of ResourceResolver"
        })
public class ResourceResolverServiceImpl implements ResourceResolverService {

    @Reference
    private ResourceResolverFactory resourceResolverFactory;

    @Override
    public ResourceResolver getResourceResolver() throws LoginException {
        return this.resourceResolverFactory.getServiceResourceResolver(
                Collections.singletonMap(ResourceResolverFactory.SUBSERVICE, "flagtickSubService"));
    }
}

> Setup configuration for flagtickSubService based on directory path: ui.config/src/main/content/jcr_root/apps/flagtick/osgiconfig/config/ org.apache.sling.serviceusermapping.impl.ServiceUserMapperImpl.cfg.json

{
  "user.mapping": [
    "flagtick.core:flagtickSubService=flagtick-service-user"
  ]
}

> Create Service Implement Generate PDF From itextpdf

FlagtickPDFService.java

package com.flagtick.core.services;

import com.itextpdf.text.DocumentException;
import java.io.IOException;
import org.apache.sling.api.resource.LoginException;

public interface FlalgtickPDFService {
    byte[] generatePDFDocument(FlagtickDto flagtickDto);
    String generatePathFromDAM(FlagtickDto flagtickDto);
    void deleteDocumentInDAM(String userId);
    String storePDFToAmazonS3();
} 

FlagtickPDFServiceImpl.java

@Override
public byte[] generatePDFDocument(FlagtickDto flagtickDto) {
	ResourceResolver resolver;
	try {
		resolver = this.resourceResolver.getResourceResolver();
		Document document = new Document(PageSize.A4, 35.0F, 35.0F, 20.0F, 20.0F);
		ByteArrayOutputStream stream = new ByteArrayOutputStream();
		PdfWriter.getInstance(document, stream);
		document.open();
		writeDocumentBody(resolver, document, flagtickDto);
		document.close();
		return stream.toByteArray();
	} catch (LoginException | DocumentException e) {
		LOGGER.error("Exception occurred: {}", e.getMessage());
	}
	return null;
}

private void writeDocumentBody(final ResourceResolver resolver, final Document document, FlagtickDto flagtickDto) {
	// Add FLAGTICK logo
	Image logo;
	Externalizer externalizer = resolver.adaptTo(Externalizer.class);
	try {
		logo = Image.getInstance(externalizer.publishLink(resolver,
				"/content/dam/flagtick/logos/logo.jpg"));
		Paragraph blankLine = new Paragraph("\n");
		LineSeparator lineSeparator = new LineSeparator();

		document.add(blankLine);
		logo.scaleAbsolute(180.0F, 60.0F);
		document.add(logo);

		Font font = FontFactory.getFont(FontFactory.HELVETICA, 14);
		Font fontBold = FontFactory.getFont(FontFactory.HELVETICA, 15, Font.BOLD);

		document.add(blankLine);
		document.add(blankLine);

		Paragraph mainTab = new Paragraph("Main Tab", fontBold);
		document.add(mainTab);
		document.add(new Chunk(lineSeparator));

		document.add(blankLine);
		Paragraph tabOne = new Paragraph("Tab One", font);
		tabOne.setIndentationLeft(26);
		document.add(tabOne);

		document.add(blankLine);
		Paragraph tabOne1 = createParagraphWithTab("Tab One 1", tabOne1Value);
		tabOne1.setSpacingAfter(16f);
		tabOne1.setIndentationLeft(42);
		document.add(tabOne1);

		Paragraph tabOne2 = createParagraphWithTab("Tab One 2", tabOne2Value);
		tabOne2.setSpacingAfter(16f);
		tabOne2.setIndentationLeft(42);
		document.add(tabOne2);

		Paragraph tabOne3 = createParagraphWithTab("Tab One 3", tabOne3Value);
		tabOne3.setSpacingAfter(16f);
		tabOne3.setIndentationLeft(42);
		document.add(tabOne3);

		Paragraph tabOne4 = createParagraphWithTab("Tab One 4", tabOne4Value);
		tabOne4.setSpacingAfter(16f);
		tabOne4.setIndentationLeft(42);
		document.add(tabOne4);

		document.add(blankLine);
		Paragraph tabTwo = new Paragraph("Tab Two", font);
		tabTwo.setIndentationLeft(26);
		document.add(tabTwo);

		document.add(blankLine);
		Paragraph tabTwo1 = createParagraphWithTab("Tab Two 1", tabTwo1Value);
		tabTwo1.setSpacingAfter(16f);
		tabTwo1.setIndentationLeft(42);
		document.add(tabTwo1);

		document.add(blankLine);
		document.add(blankLine);
		document.add(blankLine);
		document.add(blankLine);
		document.add(blankLine);
		document.add(blankLine);
		document.add(blankLine);
		document.add(blankLine);
		document.add(blankLine);
		document.add(blankLine);
		document.add(blankLine);
		document.add(blankLine);
		Font footerFont = FontFactory.getFont(FontFactory.HELVETICA, 12, new BaseColor(0, 84, 134));
		Paragraph footerParagraph = new Paragraph("103-105 Nguyen Phuoc Lan Street, Hoa Xuan Ward, Cam Le District, DN 550000 • www.flagtick.com • 972.248.887", footerFont);
		footerParagraph.setAlignment(Element.ALIGN_CENTER);
		footerParagraph.setSpacingBefore(159.0F);
		document.add(footerParagraph);
	} catch (DocumentException | IOException | ParseException e) {
		LOGGER.error("Exception occurred: {}", e.getMessage());
	}
}

public Paragraph createParagraphWithTab(String value1, String value2) {
	Paragraph paragraph = new Paragraph();
	paragraph.setTabSettings(new TabSettings(200f));
	paragraph.add(value1);
	paragraph.add(Chunk.TABBING);
	paragraph.add(value2);
	return paragraph;
}

Note:

- @Component(service = FlagtickPDFService.class, immediate = true) should be integrated into Implementation class.

- The publish instance should be starting first and foremost at port 4503. Then using externalizer.publishLink() as below:

> How to write a byte array to a file in an AEM folder

import static org.apache.sling.jcr.resource.api.JcrResourceConstants.NT_SLING_FOLDER;
import java.util.UUID;

private String getDocumentName(final String userId) {
	return UUID.randomUUID() + DASH_CHARACTER + userId + ".pdf";
}

@Override
public String generatePathFromDAM(FlagtickDto flagtickDto) {
	Binary binary = null;
	String pdfPath = StringUtils.EMPTY;
	try (ResourceResolver resourceResolver = this.resourceResolver.getResourceResolver()) {
		final Session session = resourceResolver.adaptTo(Session.class);
		final AssetManager assetManager = resourceResolver.adaptTo(AssetManager.class);
		if (null != session && null != assetManager) {
			final Node rootFolder = JcrUtils.getOrCreateByPath(ROOT_PATH, NT_SLING_FOLDER, session);
			byte[] bytes = generatePDFDocument(flagtickDto);
			binary = new BinaryImpl(bytes);
			final Asset pdfAsset = assetManager.createOrUpdateAsset(
					rootFolder.getPath() + SLASH + getDocumentName(userID),
					binary, "application/pdf", true);
			if (null != pdfAsset) {
				pdfPath = pdfAsset.getPath();
			}
			session.save();
		}
	} catch (LoginException | RepositoryException e) {
		LOGGER.error("Exception occurred: {}", e.getMessage());
	} finally {
		if (binary != null) {
			binary.dispose();
		}
	}
	return pdfPath;
}

Note: Using userID to generate the name of the file and format it as <UIID><SLASH><User ID><.pdf>. Besides, we will need to delete files after later one day to keep the AEM folder not overloaded by junked files or outdated files.

public void deleteDocumentInDAM(String userID) {
	try (ResourceResolver resourceResolver = this.resourceResolver.getResourceResolver()) {
		final Session session = resourceResolver.adaptTo(Session.class);
		if (session != null){
			Map<String, String> map = new HashMap<>();
			map.put("path", ROOT_PATH);
			map.put("type", "dam:Asset");
			map.put("nodename","*"+userID+"*");
			map.put("nodename.operation","like");
			map.put("relativedaterange.property","jcr:created");
			map.put("relativedaterange.upperBound","-1d");
			map.put("p.limit","-1");
			Query query = builder.createQuery(PredicateGroup.create(map),session);
			SearchResult searchResult = query.getResult();
			for(Hit hit : searchResult.getHits()) {
				String path = hit.getPath();
				session.removeItem(path);
				if (session.hasPendingChanges()) {
					session.save();
				}
			}
		}
	} catch (LoginException | RepositoryException e) {
		e.printStackTrace();
		LOGGER.error("Exception occurred: {}", e.getMessage());
	}
}

> How to upload byte array to S3 bucket in Java in AEM. First and foremost, we need to add the aws-java-sdk-s3 maven repository to the AEM project.

all/pom.xml

<embedded>
    <groupId>com.amazonaws</groupId>
    <artifactId>aws-java-sdk-osgi</artifactId>
    <target>/apps/flagtick-packages/application/install</target>
</embedded>
<embedded>
    <groupId>com.fasterxml.jackson.dataformat</groupId>
    <artifactId>jackson-dataformat-cbor</artifactId>
    <target>/apps/flagtick-packages/application/install</target>
</embedded>
<embedded>
    <groupId>software.amazon.ion</groupId>
    <artifactId>ion-java</artifactId>
    <target>/apps/flagtick-packages/application/install</target>
</embedded>
<embedded>
    <groupId>io.netty</groupId>
    <artifactId>netty-codec-http</artifactId>
    <target>/apps/flagtick-packages/application/install</target>
</embedded>
<embedded>
    <groupId>io.netty</groupId>
    <artifactId>netty-common</artifactId>
    <target>/apps/flagtick-packages/application/install</target>
</embedded>
<embedded>
    <groupId>io.netty</groupId>
    <artifactId>netty-buffer</artifactId>
    <target>/apps/flagtick-packages/application/install</target>
</embedded>
<embedded>
    <groupId>io.netty</groupId>
    <artifactId>netty-transport</artifactId>
    <target>/apps/flagtick-packages/application/install</target>
</embedded>
<embedded>
    <groupId>io.netty</groupId>
    <artifactId>netty-codec</artifactId>
    <target>/apps/flagtick-packages/application/install</target>
</embedded>
<embedded>
    <groupId>io.netty</groupId>
    <artifactId>netty-handler</artifactId>
    <target>/apps/flagtick-packages/application/install</target>
</embedded>
<embedded>
    <groupId>io.netty</groupId>
    <artifactId>netty-resolver</artifactId>
    <target>/apps/flagtick-packages/application/install</target>
</embedded>
<dependency>
    <groupId>com.amazonaws</groupId>
    <artifactId>aws-java-sdk-osgi</artifactId>
    <version>1.11.1034</version>
</dependency>
<dependency>
    <groupId>com.fasterxml.jackson.dataformat</groupId>
    <artifactId>jackson-dataformat-cbor</artifactId>
    <version>2.6.7</version>
</dependency>
<dependency>
    <groupId>software.amazon.ion</groupId>
    <artifactId>ion-java</artifactId>
    <version>1.0.2</version>
</dependency>
<dependency>
    <groupId>io.netty</groupId>
    <artifactId>netty-codec-http</artifactId>
    <version>4.1.61.Final</version>
</dependency>
<dependency>
    <groupId>io.netty</groupId>
    <artifactId>netty-common</artifactId>
    <version>4.1.61.Final</version>
</dependency>
<dependency>
    <groupId>io.netty</groupId>
    <artifactId>netty-buffer</artifactId>
    <version>4.1.61.Final</version>
</dependency>
<dependency>
    <groupId>io.netty</groupId>
    <artifactId>netty-transport</artifactId>
    <version>4.1.61.Final</version>
</dependency>
<dependency>
    <groupId>io.netty</groupId>
    <artifactId>netty-codec</artifactId>
    <version>4.1.61.Final</version>
</dependency>
<dependency>
    <groupId>io.netty</groupId>
    <artifactId>netty-handler</artifactId>
    <version>4.1.61.Final</version>
</dependency>
<dependency>
    <groupId>io.netty</groupId>
    <artifactId>netty-resolver</artifactId>
    <version>4.1.61.Final</version>
</dependency>

core/pom.xml

<dependency>
    <groupId>com.amazonaws</groupId>
    <artifactId>aws-java-sdk-osgi</artifactId>
    <version>1.11.1034</version>
</dependency> 

Note: You will need to implement OSGi Service in AEM at FlagtickPDFServiceImpl.java using @Component and @Designate annotation.

The version will be used for aws-java-sdk-OSGi indicates 1.11.1034 in AEM

package com.flagtick.core.services.impl;

import com.amazonaws.auth.AWSCredentials;
import com.amazonaws.auth.AWSStaticCredentialsProvider;
import com.amazonaws.auth.BasicAWSCredentials;
import com.amazonaws.regions.Regions;
import com.amazonaws.services.s3.AmazonS3;
import com.amazonaws.services.s3.AmazonS3ClientBuilder;
import com.amazonaws.services.s3.model.ObjectMetadata;
import com.amazonaws.services.s3.model.PutObjectRequest;
import com.day.cq.commons.Externalizer;
import com.itextpdf.text.BaseColor;
import com.itextpdf.text.Document;
import com.itextpdf.text.DocumentException;
import com.itextpdf.text.Element;
import com.itextpdf.text.Font;
import com.itextpdf.text.FontFactory;
import com.itextpdf.text.Image;
import com.itextpdf.text.PageSize;
import com.itextpdf.text.Paragraph;
import com.itextpdf.text.pdf.PdfWriter;
import com.flagtick.core.services.ResourceResolverService;
import com.flagtick.core.services.FlagtickPDFService;
import com.flagtick.core.services.impl.FlagtickPDFServiceImpl.AmazonS3Config;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.net.URL;
import java.util.Calendar;
import org.apache.sling.api.resource.LoginException;
import org.apache.sling.api.resource.ResourceResolver;
import org.osgi.service.component.annotations.Activate;
import org.osgi.service.component.annotations.Component;
import org.osgi.service.component.annotations.Modified;
import org.osgi.service.component.annotations.Reference;
import org.osgi.service.metatype.annotations.AttributeDefinition;
import org.osgi.service.metatype.annotations.AttributeType;
import org.osgi.service.metatype.annotations.Designate;
import org.osgi.service.metatype.annotations.ObjectClassDefinition;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

@Component(service = FlagtickPDFService.class, immediate = true)
@Designate(ocd = AmazonS3Config.class)
public class FlagtickPDFServiceImpl implements FlagtickPDFService {
    private final Logger LOGGER = LoggerFactory.getLogger(FlagtickPDFServiceImpl.class);
    private static final String ROOT_PATH = "/content/dam/flagtick/pdffolder";
    @Reference
private ResourceResolverService resourceResolver;
    private String accessKey;
    private String secretKey;
    private String bucketName;
    private String region;

    @Activate
    @Modified
    protected void activate(AmazonS3Config config) {
        accessKey = config.accessKey();
        secretKey = config.secretKey();
        bucketName = config.bucketName();
        region = config.region();
    }

    @ObjectClassDefinition(
            name = "FLAGTICK - Flagtick PDF Service Configurations",
            description = "Flagtick PDF Service Configurations"
    )
    public @interface AmazonS3Config {
        @AttributeDefinition(
                name = "Access Key",
                description = "Access Key of Amazon S3",
                type = AttributeType.STRING
        )
        String accessKey();

        @AttributeDefinition(
                name = "Secret Key",
                description = "Secret Key of Amazon S3",
                type = AttributeType.STRING
        )
        String secretKey();

        @AttributeDefinition(
                name = "Bucket Name",
                description = "Bucket Name of Amazon S3",
                type = AttributeType.STRING
        )
        String bucketName();

        @AttributeDefinition(
                name = "Region",
                description = "Region of the amazon s3 instance"
        )
        String region();
    }
} 

> Write Function to Store File in bucket Amazon S3

 @Override
public String storePDFToAmazonS3(FlagtickDto flagtickDto) {
	ByteArrayInputStream inputStream = new ByteArrayInputStream(this.generatePDFDocument(FlagtickDto flagtickDto));
	String filePath = getDocumentName(personID);
	ObjectMetadata objectMetadata = new ObjectMetadata();
	objectMetadata.setContentType("application/pdf");
	PutObjectRequest putObjectRequest = new PutObjectRequest("aemproject", filePath, inputStream, objectMetadata);
	AmazonS3 amazonS3 = getAmazonS3Client();
	amazonS3.putObject(putObjectRequest);
	Calendar now = Calendar.getInstance();
	now.add(12, 3);
	URL url = amazonS3.generatePresignedUrl("aemproject", filePath, now.getTime());
	return url.toString();
}


private AmazonS3 getAmazonS3Client() {
	AWSCredentials awsCredentials = new BasicAWSCredentials("XXXXXXXXXXXXXXX", "rxmx07+QQTRc3lXj+bSVCXXXXXXXXXXXl9Xy");


	return AmazonS3ClientBuilder.standard()
			.withCredentials(new AWSStaticCredentialsProvider(awsCredentials))
			.withRegion(Regions.fromName("ap-southeast-1"))
			.build();
}

> Establish Cloud Manager Environment Variables

ui.config/src/main/content/jcr_root/apps/flagtick/osgiconfig/config/ com.flagtick.core.services.impl.FlagtickPDFServiceImpl.cfg.json

{
  "accessKey": "XXXXXXXXXXXXXXXXX",
  "secretKey": "XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX",
  "bucketName": "",
  "region": "ap-southeast-1"
}

{
  "accessKey": "$[env:ACCESS_KEY]",
  "secretKey": "$[env:SECRET_KEY]",
  "bucketName": "$[env:BUCKET_NAME]",
  "region": "ap-southeast-1"
}

> Logged into Amazon S3 and re-check

> Finally, we have an URL https://aemproject.s3.ap-southeast-1.amazonaws.com/eb22b68b-065b-4401-ac5b-9f04d0aa0f87-11111.pdf

You need to login to do this manipulation!