> 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