AEM Cloud Service - Convert AWS SDK Jar to OSGI, Add as Embedded Dependency, Upload CSV files to S3

Goal

Convert to OSGI and Embed AWS SDK Jar as dependency in your AEM Cloud Services project, use the AWS S3 api to upload small csv files generated in AEM, to S3 bucket (for consumption by a third-party)

Package Install | AWS SDK OSGI Jar | Github

Initiate Upload to S3 : https://author-pxxxxx-exxxxx.adobeaemcloud.com/bin/eaem/s3/upload?path=/content/dam/eaem-s3-upload/experience-aem.csv



Step 1 : Create the maven project

set JAVA_HOME=C:/Progra~1/Java/jdk-11.0.6

mvn -B org.apache.maven.plugins:maven-archetype-plugin:3.2.1:generate -D archetypeGroupId=com.adobe.aem
-D archetypeArtifactId=aem-project-archetype -D archetypeVersion=36 -D aemVersion="cloud"
-D appTitle="Experience AEM S3 Upload" -D appId="eaem-s3-upload" -D groupId="apps.experienceaem.assets"
-D frontendModule=none -D includeExamples=n -D includeDispatcherConfig=n


Step 2 : Convert the AWS SDK Jar to OSGI

1) Download the AWS SDK Jar from Maven Centralhttps://repo1.maven.org/maven2/com/amazonaws/aws-java-sdk-bundle/1.12.470/aws-java-sdk-bundle-1.12.470.jar

2) Add the AWS SDK jar downloaded, in eaem-s3-upload\packages\aws-java-sdk-bundle-1.12.470.jar

3) Get the bnd tool jar - https://repo1.maven.org/maven2/biz/aQute/bnd/biz.aQute.bnd/6.4.0 and place it in eaem-s3-upload\packages

4) Create the bnd config file eaem-s3-upload\packages\aws-osgi.bnd (not much experience here, it could be more targeted...)

-classpath: aws-java-sdk-bundle-1.12.470.jar
-output: aws-java-sdk-bundle-osgi-1.12.470.jar
Export-Package: *
Import-Package: *
Bundle-Version: 1.12.470
Bundle-Name: AWS SDK For Java for OSGi
Bundle-SymbolicName: com.amazonaws.aws-java-sdk-osgi

4)  Run java -jar biz.aQute.bnd-6.4.0.jar aws-osgi.bnd to generate the OSGI jar eaem-s3-upload\packages\aws-java-sdk-bundle-osgi-1.12.470.jar

5) The OSGI jar isn't activating in /system/console/bundles complaining about missing dependencies, there must be a better way, but i had to do some surgery in aws-java-sdk-bundle-osgi-1.12.470.jar\META-INF\MANIFEST.MF, remove the unavailable dependencies from Import-Package


Step 3 : Embed the AWS SDK OSGI Jar 

1) Copy the generated AWS SDK OSGI jar to eaem-s3-upload\dependencies\aws-java-sdk-bundle-osgi-1.12.470.jar

2) Add the AWS SDK Maven dependency in eaem-s3-upload\pom.xml

<dependency>
<groupId>com.amazonaws</groupId>
<artifactId>aws-java-sdk-bundle</artifactId>
<version>1.12.470</version>
<scope>provided</scope>
</dependency>

3) Add the AWS SDK Maven dependency in eaem-s3-upload\core\pom.xml

<dependency>
<groupId>com.amazonaws</groupId>
<artifactId>aws-java-sdk-bundle</artifactId>
</dependency>

4) Embed the AWS SDK OSGI Jar using the following configuration in eaem-s3-upload\all\pom.xml

<embedded>
<groupId>com.amazonaws</groupId>
<artifactId>aws-java-sdk-bundle-osgi</artifactId>
<target>/apps/eaem-s3-upload-packages/application/install</target>
</embedded>
<dependency>
<groupId>com.amazonaws</groupId>
<artifactId>aws-java-sdk-bundle-osgi</artifactId>
<version>1.12.470</version>
<scope>system</scope>
<systemPath>${pom.basedir}/../dependencies/aws-java-sdk-bundle-osgi-1.12.470.jar</systemPath>
</dependency>

5) There should be a better way, but you may have to comment the aemanalyser-maven-plugin in eaem-s3-upload\all\pom.xml


Step 4 : S3 Upload Implementation

1) Add the service apps.experienceaem.assets.core.services.impl.S3ServiceImpl with necessary code for uploading to S3

package apps.experienceaem.assets.core.services.impl;

import apps.experienceaem.assets.core.services.S3Service;
import com.amazonaws.HttpMethod;
import com.amazonaws.auth.AWSStaticCredentialsProvider;
import com.amazonaws.auth.BasicAWSCredentials;
import com.amazonaws.services.s3.AmazonS3;
import com.amazonaws.services.s3.AmazonS3ClientBuilder;
import com.amazonaws.services.s3.model.GeneratePresignedUrlRequest;
import org.apache.commons.io.IOUtils;
import org.apache.sling.api.resource.Resource;
import org.apache.sling.api.resource.ValueMap;
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.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;

import java.io.*;
import java.net.HttpURLConnection;
import java.net.URL;
import java.util.Date;

@Component(service = S3Service.class)
@Designate(ocd = S3ServiceImpl.S3Configuration.class)
public class S3ServiceImpl implements S3Service{
private final Logger logger = LoggerFactory.getLogger(S3ServiceImpl.class);

private String s3BucketName = "";
private String s3AccessKey = "";
private String s3SecretKey = "";
private String s3Region = "";

private URL getUploadPUTPresignedUrl(String fileName){
AmazonS3 s3Client = AmazonS3ClientBuilder.standard()
.withCredentials(new AWSStaticCredentialsProvider(new BasicAWSCredentials(s3AccessKey, s3SecretKey)))
.withRegion(s3Region).build();

Date expiration = new Date();

long expTimeMillis = expiration.getTime() + (24 * 1000 * 60 * 60);
expiration.setTime(expTimeMillis);

GeneratePresignedUrlRequest presignedPut = new GeneratePresignedUrlRequest(s3BucketName, fileName)
.withMethod(HttpMethod.PUT)
.withExpiration(expiration);

return s3Client.generatePresignedUrl(presignedPut);
}

@Activate
@Modified
protected void activate(final S3Configuration config) {
s3BucketName = config.s3BucketName();
s3AccessKey = config.s3AccessKey();
s3SecretKey = config.s3SecretKey();
s3Region = config.s3Region();
}

public void uploadToS3(Resource csvResource){
InputStream assetStream = null;
FileOutputStream out = null;
BufferedOutputStream awsOut = null;
String tempFilePath = null;

try {
assetStream = (InputStream) csvResource.getChild("jcr:content/renditions/original/jcr:content").adaptTo(ValueMap.class).get("jcr:data");

@SuppressWarnings({"findsecbugs:PATH_TRAVERSAL_OUT", "findsecbugs:PATH_TRAVERSAL_IN"})
File tempFile = File.createTempFile(csvResource.getName(), ".temp");
tempFilePath = tempFile.getPath();

out = new FileOutputStream(tempFile);
IOUtils.copy(assetStream, out);
}catch(Exception e) {
logger.error("Error uploading to S3 - " + e);
throw new RuntimeException("Error uploading to S3", e);
}finally {
try {
if(out != null) { out.close(); }
} catch (IOException e) {
logger.warn("Error closing streams", e);
}
}

@SuppressWarnings({"findsecbugs:PATH_TRAVERSAL_OUT", "findsecbugs:PATH_TRAVERSAL_IN"})
File tempFile = new File(tempFilePath);

try (FileInputStream csvIS = new FileInputStream(tempFile)){
HttpURLConnection connection = (HttpURLConnection) getUploadPUTPresignedUrl(csvResource.getName()).openConnection();
connection.setConnectTimeout(10000);
connection.setReadTimeout(10000);
connection.setDoOutput(true);
connection.setRequestProperty("Content-Type", "text/csv");
connection.setRequestMethod("PUT");

awsOut = new BufferedOutputStream(connection.getOutputStream());

IOUtils.copy(csvIS, awsOut);

awsOut.close();

int resCode = connection.getResponseCode();

if(resCode != 200){
throw new RuntimeException("Error uploading file to S3, received response " + resCode);
}

if(!tempFile.delete()){
logger.warn("Error deleting temp file from local file system after uploading to S3 - " + tempFile.getPath());
}
}catch(Exception e){
logger.error("Error uploading to S3 - " + e);
throw new RuntimeException("Error uploading to S3", e);
} finally {
try {
if(assetStream != null) { assetStream.close(); }
if(awsOut != null) { awsOut.close(); }
} catch (IOException e) {
logger.warn("Error closing streams", e);
}
}
}

@ObjectClassDefinition(name = "Amazon S3 Configuration")
public @interface S3Configuration {
@AttributeDefinition(
name = "S3 Bucket Name",
description = "S3 Bucket Name for uploading files",
type = AttributeType.STRING
)
String s3BucketName() default "experience-aem";

@AttributeDefinition(
name = "S3 Access Key",
description = "S3 Access Key for S3 bucket access",
type = AttributeType.STRING
)
String s3AccessKey() default "";

@AttributeDefinition(
name = "S3 Secret Key",
description = "S3 Secret Key for S3 bucket access",
type = AttributeType.STRING
)
String s3SecretKey() default "";

@AttributeDefinition(
name = "S3 Region",
description = "S3 Region for S3 bucket access",
type = AttributeType.STRING
)
String s3Region() default "us-east-2";
}
}

2) Add a servlet apps.experienceaem.assets.core.servlets.UploadToS3Servlet to initiate the upload call...

package apps.experienceaem.assets.core.servlets;

import apps.experienceaem.assets.core.services.S3Service;
import com.adobe.xfa.ut.StringUtils;
import org.apache.sling.api.SlingHttpServletRequest;
import org.apache.sling.api.SlingHttpServletResponse;
import org.apache.sling.api.resource.Resource;
import org.apache.sling.api.servlets.SlingAllMethodsServlet;
import org.osgi.service.component.annotations.Component;
import org.osgi.service.component.annotations.Reference;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import javax.servlet.Servlet;
import javax.servlet.ServletException;
import java.io.IOException;

@Component(
name = "Experience AEM Upload to S3 Servlet",
immediate = true,
service = Servlet.class,
property = {
"sling.servlet.methods=GET",
"sling.servlet.paths=/bin/eaem/s3/upload"
}
)
public class UploadToS3Servlet extends SlingAllMethodsServlet {
private static final Logger LOGGER = LoggerFactory.getLogger(UploadToS3Servlet.class);

@Reference
transient S3Service s3Service;

@Override
protected void doGet(SlingHttpServletRequest request, SlingHttpServletResponse response)
throws ServletException, IOException {

try{
String path = request.getParameter("path");

if(StringUtils.isEmpty(path) || !path.endsWith(".csv")){
response.getWriter().println("'path' parameter missing in request or not a csv");
return;
}

Resource csvRes = request.getResourceResolver().getResource(path);

if(csvRes == null){
response.getWriter().println("No csv or no access to the csv : " + path);
return;
}

s3Service.uploadToS3(csvRes);

response.getWriter().println("Done uploading to S3 : " + path);
}catch(Exception e){
throw new ServletException("Error", e);
}
}
}

3) Configure the necessary Environment Variables in Cloud Manager


4) For testing with Local AEM SDK, add the env variables in start script using set (windows)


5) Initiate the upload process by accessing https://author-pxxxxx-exxxxx.adobeaemcloud.com/bin/eaem/s3/upload?path=/content/dam/eaem-s3-upload/experience-aem.csv

No comments:

Post a Comment