AWS rekognition Request has invalid image format - amazon-web-services

I get this error when i try to compare two images from my s3 bucket, i follow the functions rules to get an image from S3Object with correct name and s3 bucket name but i throws the Invalid image format exception. Maybe it has to be as a base64 or bytebuffer? i dont understand then why there is a function to get from S3Object.
My code is simple and as follows:
String reference = "reference.jpg";
String target = "selfie.jpg";
String bucket = "pruebas";
CompareFacesRequest request = new CompareFacesRequest()
.withSourceImage(new Image().withS3Object(new S3Object()
.withName(reference).withBucket(bucket)))
.withTargetImage(new Image().withS3Object(new S3Object()
.withName(target).withBucket(bucket)))
.withSimilarityThreshold(similarityThreshold);
AmazonRekognition rekognitionClient = AmazonRekognitionClientBuilder.standard()
.withRegion(Regions.US_EAST_1).build();
CompareFacesResult compareFacesResult= rekognitionClient.compareFaces(request);
The exception is thrown in compareFaces(request) the last line.
this is main part of the error:
Exception in thread "main" com.amazonaws.services.rekognition.model.InvalidImageFormatException: Request has invalid image format (Service: AmazonRekognition; Status Code: 400; Error Code: InvalidImageFormatException;
The images are in AWS S3 and my credentials for rekognition have permission to read from S3. So in that part is not the error.
UPDATE CODE:
public static void main(String[] args) throws FileNotFoundException {
Float similarityThreshold = 70F;
String reference = "reference.jpg";
String target = "target.jpg";
String bucket = "pruebas";
ProfileCredentialsProvider credentialsProvider = ProfileCredentialsProvider.builder().profileName("S3").build();
Region region = Region.US_EAST_1;
S3Client s3 = S3Client.builder()
.region(region)
.credentialsProvider(credentialsProvider)
.build();
byte[] sourceStream = getObjectBytes(s3, bucket,reference);
byte[] tarStream = getObjectBytes(s3, bucket, target);
SdkBytes sourceBytes = SdkBytes.fromByteArrayUnsafe(sourceStream);
SdkBytes targetBytes = SdkBytes.fromByteArrayUnsafe(tarStream);
Image souImage = Image.builder()
.bytes(sourceBytes)
.build();
Image tarImage = Image.builder()
.bytes(targetBytes)
.build();
CompareFacesRequest request = CompareFacesRequest.builder()
.sourceImage(souImage)
.targetImage(tarImage)
.similarityThreshold(similarityThreshold).build();
RekognitionClient rekognitionClient = RekognitionClient.builder()
.region(Region.US_EAST_2).build();
CompareFacesResponse compareFacesResult= rekognitionClient.compareFaces(request);
List<CompareFacesMatch> faceDetails = compareFacesResult.faceMatches();
for (CompareFacesMatch match: faceDetails){
ComparedFace face= match.face();
BoundingBox position = face.boundingBox();
System.out.println("Face at " + position.left().toString()
+ " " + position.top()
+ " matches with " + face.confidence().toString()
+ "% confidence.");
}
List<ComparedFace> uncompared = compareFacesResult.unmatchedFaces();
System.out.println("There was " + uncompared.size()
+ " face(s) that did not match");
System.out.println("Source image rotation: " + compareFacesResult.sourceImageOrientationCorrection());
System.out.println("target image rotation: " + compareFacesResult.targetImageOrientationCorrection());
}
public static byte[] getObjectBytes (S3Client s3, String bucketName, String keyName) {
try {
GetObjectRequest objectRequest = GetObjectRequest
.builder()
.key(keyName)
.bucket(bucketName)
.build();
ResponseBytes<GetObjectResponse> objectBytes = s3.getObjectAsBytes(objectRequest);
return objectBytes.asByteArray();
} catch (S3Exception e) {
System.err.println(e.awsErrorDetails().errorMessage());
System.exit(1);
}
return null;
}

Try using AWS SDK for Java V2 - not the old V1 lib. Using V2 is strongly recommended over V1 and is best practice.
Here is the V2 code that works fine to compare faces. In this example, notice that you have to get the image into a SdkBytes object. It does not matter where the image is located as long as you get it into SDKBytes. The image can be in an S3 bucket, the local file system. etc.
You can find this V2 Reckonation example in the AWS Github repo here:
https://github.com/awsdocs/aws-doc-sdk-examples/blob/main/javav2/example_code/rekognition/src/main/java/com/example/rekognition
Also - you can use S3 Java V2 Service client to read an image in an S3 bucket and get the byte[]. From there, you can create an SDKBytes object to use in CompareFaces.
https://github.com/awsdocs/aws-doc-sdk-examples/blob/main/javav2/example_code/s3/src/main/java/com/example/s3/GetObjectData.java
Here is the full Java example for Compare Faces. Notice there is an URL to the Java DEV Guide if you do not know how to get up and running with AWS SDK for Java V2 - including how to set up your creds.
package com.example.rekognition;
// snippet-start:[rekognition.java2.compare_faces.import]
import software.amazon.awssdk.auth.credentials.ProfileCredentialsProvider;
import software.amazon.awssdk.regions.Region;
import software.amazon.awssdk.services.rekognition.RekognitionClient;
import software.amazon.awssdk.services.rekognition.model.RekognitionException;
import software.amazon.awssdk.services.rekognition.model.Image;
import software.amazon.awssdk.services.rekognition.model.CompareFacesRequest;
import software.amazon.awssdk.services.rekognition.model.CompareFacesResponse;
import software.amazon.awssdk.services.rekognition.model.CompareFacesMatch;
import software.amazon.awssdk.services.rekognition.model.ComparedFace;
import software.amazon.awssdk.services.rekognition.model.BoundingBox;
import software.amazon.awssdk.core.SdkBytes;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.InputStream;
import java.util.List;
// snippet-end:[rekognition.java2.compare_faces.import]
/**
* Before running this Java V2 code example, set up your development environment, including your credentials.
*
* For more information, see the following documentation topic:
*
* https://docs.aws.amazon.com/sdk-for-java/latest/developer-guide/get-started.html
*/
public class CompareFaces {
public static void main(String[] args) {
final String usage = "\n" +
"Usage: " +
" <pathSource> <pathTarget>\n\n" +
"Where:\n" +
" pathSource - The path to the source image (for example, C:\\AWS\\pic1.png). \n " +
" pathTarget - The path to the target image (for example, C:\\AWS\\pic2.png). \n\n";
if (args.length != 2) {
System.out.println(usage);
System.exit(1);
}
Float similarityThreshold = 70F;
String sourceImage = args[0];
String targetImage = args[1];
Region region = Region.US_EAST_1;
RekognitionClient rekClient = RekognitionClient.builder()
.region(region)
.credentialsProvider(ProfileCredentialsProvider.create())
.build();
compareTwoFaces(rekClient, similarityThreshold, sourceImage, targetImage);
rekClient.close();
}
// snippet-start:[rekognition.java2.compare_faces.main]
public static void compareTwoFaces(RekognitionClient rekClient, Float similarityThreshold, String sourceImage, String targetImage) {
try {
InputStream sourceStream = new FileInputStream(sourceImage);
InputStream tarStream = new FileInputStream(targetImage);
SdkBytes sourceBytes = SdkBytes.fromInputStream(sourceStream);
SdkBytes targetBytes = SdkBytes.fromInputStream(tarStream);
// Create an Image object for the source image.
Image souImage = Image.builder()
.bytes(sourceBytes)
.build();
Image tarImage = Image.builder()
.bytes(targetBytes)
.build();
CompareFacesRequest facesRequest = CompareFacesRequest.builder()
.sourceImage(souImage)
.targetImage(tarImage)
.similarityThreshold(similarityThreshold)
.build();
// Compare the two images.
CompareFacesResponse compareFacesResult = rekClient.compareFaces(facesRequest);
List<CompareFacesMatch> faceDetails = compareFacesResult.faceMatches();
for (CompareFacesMatch match: faceDetails){
ComparedFace face= match.face();
BoundingBox position = face.boundingBox();
System.out.println("Face at " + position.left().toString()
+ " " + position.top()
+ " matches with " + face.confidence().toString()
+ "% confidence.");
}
List<ComparedFace> uncompared = compareFacesResult.unmatchedFaces();
System.out.println("There was " + uncompared.size() + " face(s) that did not match");
System.out.println("Source image rotation: " + compareFacesResult.sourceImageOrientationCorrection());
System.out.println("target image rotation: " + compareFacesResult.targetImageOrientationCorrection());
} catch(RekognitionException | FileNotFoundException e) {
System.out.println("Failed to load source image " + sourceImage);
System.exit(1);
}
}
// snippet-end:[rekognition.java2.compare_faces.main]
}
This code works fine too with JPG images -- as shown in the following image of an IDE debugging the code displaying the results:
UPDATE
I normally do not touch V1 code at all; however, i was curious. This code worked....
package aws.example.rekognition.image;
import com.amazonaws.regions.Regions;
import com.amazonaws.services.rekognition.AmazonRekognition;
import com.amazonaws.services.rekognition.AmazonRekognitionClientBuilder;
import com.amazonaws.services.rekognition.model.Image;
import com.amazonaws.services.rekognition.model.BoundingBox;
import com.amazonaws.services.rekognition.model.CompareFacesMatch;
import com.amazonaws.services.rekognition.model.CompareFacesRequest;
import com.amazonaws.services.rekognition.model.CompareFacesResult;
import com.amazonaws.services.rekognition.model.ComparedFace;
import java.util.List;
import com.amazonaws.services.rekognition.model.S3Object;
public class CompareFacesBucket {
public static void main(String[] args) throws Exception{
Float similarityThreshold = 70F;
AmazonRekognition rekognitionClient = AmazonRekognitionClientBuilder.standard()
.withRegion(Regions.US_WEST_2)
.build();
String reference = "Lam1.jpg";
String target = "Lam2.jpg";
String bucket = "<MyBucket>";
CompareFacesRequest request = new CompareFacesRequest()
.withSourceImage(new Image().withS3Object(new S3Object()
.withName(reference).withBucket(bucket)))
.withTargetImage(new Image().withS3Object(new S3Object()
.withName(target).withBucket(bucket)))
.withSimilarityThreshold(similarityThreshold);
// Call operation
CompareFacesResult compareFacesResult = rekognitionClient.compareFaces(request);
// Display results
List <CompareFacesMatch> faceDetails = compareFacesResult.getFaceMatches();
for (CompareFacesMatch match: faceDetails){
ComparedFace face= match.getFace();
BoundingBox position = face.getBoundingBox();
System.out.println("Face at " + position.getLeft().toString()
+ " " + position.getTop()
+ " matches with " + face.getConfidence().toString()
+ "% confidence.");
}
List<ComparedFace> uncompared = compareFacesResult.getUnmatchedFaces();
System.out.println("There was " + uncompared.size()
+ " face(s) that did not match");
System.out.println("Source image rotation: " + compareFacesResult.getSourceImageOrientationCorrection());
System.out.println("target image rotation: " + compareFacesResult.getTargetImageOrientationCorrection());
}
}
Output:
The last thing that i can think of as my V1 code works with JPG images and yours does not is your JPG image files may be corrupt or something. I would like to test this code with your images.

Related

How to enter data from Spring Boot Application into Amazon Kinesis?

I want to add data into kinesis using Sprint Boot Application and React. I am a complete beginner when it comes to Kinesis, AWS, etc. so a beginner friendly guide would be appriciated.
To add data records into an Amazon Kinesis data stream from a Spring BOOT app, you can use the AWS SDK for Java V2 and specifically the Amazon Kinesis Java API. You can use the software.amazon.awssdk.services.kinesis.KinesisClient.
Because you are a beginner, I recommend that you read the AWS SDK Java V2 Developer Guide to become familiar with how to work with this Java API. See Developer guide - AWS SDK for Java 2.x.
Here is a code example that shows you how to add data records using this Service Client. See Github that has the other required classes here.
package com.example.kinesis;
//snippet-start:[kinesis.java2.putrecord.import]
import software.amazon.awssdk.auth.credentials.ProfileCredentialsProvider;
import software.amazon.awssdk.core.SdkBytes;
import software.amazon.awssdk.regions.Region;
import software.amazon.awssdk.services.kinesis.KinesisClient;
import software.amazon.awssdk.services.kinesis.model.PutRecordRequest;
import software.amazon.awssdk.services.kinesis.model.KinesisException;
import software.amazon.awssdk.services.kinesis.model.DescribeStreamRequest;
import software.amazon.awssdk.services.kinesis.model.DescribeStreamResponse;
//snippet-end:[kinesis.java2.putrecord.import]
/**
* Before running this Java V2 code example, set up your development environment, including your credentials.
*
* For more information, see the following documentation topic:
*
* https://docs.aws.amazon.com/sdk-for-java/latest/developer-guide/get-started.html
*/
public class StockTradesWriter {
public static void main(String[] args) {
final String usage = "\n" +
"Usage:\n" +
" <streamName>\n\n" +
"Where:\n" +
" streamName - The Amazon Kinesis data stream to which records are written (for example, StockTradeStream)\n\n";
if (args.length != 1) {
System.out.println(usage);
System.exit(1);
}
String streamName = args[0];
Region region = Region.US_EAST_1;
KinesisClient kinesisClient = KinesisClient.builder()
.region(region)
.credentialsProvider(ProfileCredentialsProvider.create())
.build();
// Ensure that the Kinesis Stream is valid.
validateStream(kinesisClient, streamName);
setStockData( kinesisClient, streamName);
kinesisClient.close();
}
// snippet-start:[kinesis.java2.putrecord.main]
public static void setStockData( KinesisClient kinesisClient, String streamName) {
try {
// Repeatedly send stock trades with a 100 milliseconds wait in between
StockTradeGenerator stockTradeGenerator = new StockTradeGenerator();
// Put in 50 Records for this example
int index = 50;
for (int x=0; x<index; x++){
StockTrade trade = stockTradeGenerator.getRandomTrade();
sendStockTrade(trade, kinesisClient, streamName);
Thread.sleep(100);
}
} catch (KinesisException | InterruptedException e) {
System.err.println(e.getMessage());
System.exit(1);
}
System.out.println("Done");
}
private static void sendStockTrade(StockTrade trade, KinesisClient kinesisClient,
String streamName) {
byte[] bytes = trade.toJsonAsBytes();
// The bytes could be null if there is an issue with the JSON serialization by the Jackson JSON library.
if (bytes == null) {
System.out.println("Could not get JSON bytes for stock trade");
return;
}
System.out.println("Putting trade: " + trade);
PutRecordRequest request = PutRecordRequest.builder()
.partitionKey(trade.getTickerSymbol()) // We use the ticker symbol as the partition key, explained in the Supplemental Information section below.
.streamName(streamName)
.data(SdkBytes.fromByteArray(bytes))
.build();
try {
kinesisClient.putRecord(request);
} catch (KinesisException e) {
e.getMessage();
}
}
private static void validateStream(KinesisClient kinesisClient, String streamName) {
try {
DescribeStreamRequest describeStreamRequest = DescribeStreamRequest.builder()
.streamName(streamName)
.build();
DescribeStreamResponse describeStreamResponse = kinesisClient.describeStream(describeStreamRequest);
if(!describeStreamResponse.streamDescription().streamStatus().toString().equals("ACTIVE")) {
System.err.println("Stream " + streamName + " is not active. Please wait a few moments and try again.");
System.exit(1);
}
}catch (KinesisException e) {
System.err.println("Error found while describing the stream " + streamName);
System.err.println(e);
System.exit(1);
}
}
// snippet-end:[kinesis.java2.putrecord.main]
}

fetch batch translate job details using SDK

I was trying to find the LanguagePair and Operation associated with a given AWS translate job using the JAVA SDK.
Using the AWS web console, i created a couple of batch jobs to translate a few english sentences to french. In CloudWatch, i could see the metric dimensions as
LanguagePair: en-fr
Operation: TranslateText
Can i retrieve the same information (LanguagePair and Operation) for a given job,
using the TranslateAsyncClient.describeTextTranslationJob(...) method ?
You can use the TranslateClient to describes a translation job given the job number as input. This uses the TranslateClient; however you can use the Async version as well.
import software.amazon.awssdk.regions.Region;
import software.amazon.awssdk.services.translate.TranslateClient;
import software.amazon.awssdk.services.translate.model.DescribeTextTranslationJobRequest;
import software.amazon.awssdk.services.translate.model.DescribeTextTranslationJobResponse;
import software.amazon.awssdk.services.translate.model.TranslateException;
// snippet-end:[translate.java2._describe_jobs.import]
/**
* To run this Java V2 code example, ensure that you have setup your development environment, including your credentials.
*
* For information, see this documentation topic:
*
* https://docs.aws.amazon.com/sdk-for-java/latest/developer-guide/get-started.html
*/
public class DescribeTextTranslationJob {
public static void main(String[] args) {
final String USAGE = "\n" +
"Usage:\n" +
" DescribeTextTranslationJob <id> \n\n" +
"Where:\n" +
" id - a translation job ID value. You can obtain this value from the BatchTranslation example.\n";
if (args.length != 1) {
System.out.println(USAGE);
System.exit(1);
}
String id = args[0];
Region region = Region.US_WEST_2;
TranslateClient translateClient = TranslateClient.builder()
.region(region)
.build();
describeTextTranslationJob(translateClient, id);
translateClient.close();
}
// snippet-start:[translate.java2._describe_jobs.main]
public static void describeTextTranslationJob(TranslateClient translateClient, String id) {
try {
DescribeTextTranslationJobRequest textTranslationJobRequest = DescribeTextTranslationJobRequest.builder()
.jobId(id)
.build();
DescribeTextTranslationJobResponse jobResponse = translateClient.describeTextTranslationJob(textTranslationJobRequest);
System.out.println("The job status is "+jobResponse.textTranslationJobProperties().jobStatus());
System.out.println("The source language is "+jobResponse.textTranslationJobProperties().sourceLanguageCode());
System.out.println("The target language is "+jobResponse.textTranslationJobProperties().targetLanguageCodes());
} catch (TranslateException e) {
System.err.println(e.getMessage());
System.exit(1);
}
// snippet-end:[translate.java2._describe_jobs.main]
}
}
To see all the data you can get back using this code, see this JavaDoc - https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/services/translate/model/TextTranslationJobProperties.html

AmazonClientException: More data read has a different length the expected

When I try to upload content to an Amazon S3 bucket, I get an AmazonClientException: Data read has a different length than the expected.
Here is my code.
public Object uploadFile(MultipartFile file) {
String fileName = System.currentTimeMillis() + "_" + file.getOriginalFilename();
log.info("uploadFile-> starting file upload " + fileName);
Path path = Paths.get(file.getOriginalFilename());
File fileObj = new File(file.getOriginalFilename());
try (FileOutputStream os = new FileOutputStream(fileObj)) {
os.write(file.getBytes());
os.close();
String uploadFilePath = bucketName + "/" + uploadPath;
s3Client.putObject(new PutObjectRequest(uploadFilePath, fileName, fileObj));
Files.delete(path);
} catch (IOException ex) {
log.error("error [" + ex.getMessage() + "] occurred while uploading [" + fileName + "] ");
}
log.info("uploadFile-> file uploaded process completed at: " + LocalDateTime.now() + " for - " + fileName);
return "File uploaded : " + fileName;
}
Amazon recommends using the Amazon S3 Java V2 API over use of V1.
The AWS SDK for Java 2.x is a major rewrite of the version 1.x code base. It’s built on top of Java 8+ and adds several frequently requested features. These include support for non-blocking I/O and the ability to plug in a different HTTP implementation at run time.
To upload content to an Amazon S3 bucket, use this V2 code.
public static String putS3Object(S3Client s3,
String bucketName,
String objectKey,
String objectPath) {
try {
Map<String, String> metadata = new HashMap<>();
metadata.put("myVal", "test");
PutObjectRequest putOb = PutObjectRequest.builder()
.bucket(bucketName)
.key(objectKey)
.metadata(metadata)
.build();
PutObjectResponse response = s3.putObject(putOb,
RequestBody.fromBytes(getObjectFile(objectPath)));
return response.eTag();
} catch (S3Exception e) {
System.err.println(e.getMessage());
System.exit(1);
}
return "";
}
Full example here.
If you are not familiar with V2, please refer to this doc topic:
https://docs.aws.amazon.com/sdk-for-java/latest/developer-guide/get-started.html
AWS Java SDK 1.x
s3.putObject(bucketName, key, new File(filePath));
AWS Java SDK 2.X
PutObjectRequest putObjectRequest = PutObjectRequest
.builder()
.bucket(bucketName)
.key(key)
.build();
s3Client.putObject(putObjectRequest, Paths.get(filePath));

AWS QuickSight programmatic access

I have been recently involved in a project where I have to leverage the QuickSight APIs and update a dashboard programmatically. I can perform all the other actions but I am unable to update the dashboard from a template. I have tried a couple of different ideas, but all in vain.
Is there anyone who has already worked with the UpdateDashboard API or point me to some detailed documentation where I can understand if I am actually missing anything?
Thanks.
I got this to work using the AWS QuickSight Java V2 API. TO make this work, you need to follow the quick start instructions here:
https://docs.aws.amazon.com/quicksight/latest/user/getting-started.html
You need to get these values:
account - your account number
dashboardId - the dashboard id value
dataSetArn -- the data set ID value
analysisArn - the analysis Arn value
Once you go through the above topics - you will have all of these resource and ready to call UpdateDashboard . Here is the Java example that updates a Dashboard.
package com.example.quicksight;
import software.amazon.awssdk.regions.Region;
import software.amazon.awssdk.services.quicksight.QuickSightClient;
import software.amazon.awssdk.services.quicksight.model.*;
/*
Before running this code example, follow the Getting Started with Data Analysis in Amazon QuickSight located here:
https://docs.aws.amazon.com/quicksight/latest/user/getting-started.html
This code example uses resources that you created by following that topic such as the DataSet Arn value.
*/
public class UpdateDashboard {
public static void main(String[] args) {
final String USAGE = "\n" +
"Usage: UpdateDashboard <account> <dashboardId> <>\n\n" +
"Where:\n" +
" account - the account to use.\n\n" +
" dashboardId - the dashboard id value to use.\n\n" +
" dataSetArn - the ARN of the dataset.\n\n" +
" analysisArn - the ARN of an existing analysis";
String account = "<account id>";
String dashboardId = "<dashboardId>";
String dataSetArn = "<dataSetArn>";
String analysisArn = "<Analysis Arn>";
QuickSightClient qsClient = QuickSightClient.builder()
.region(Region.US_EAST_1)
.build();
try {
DataSetReference dataSetReference = DataSetReference.builder()
.dataSetArn(dataSetArn)
.dataSetPlaceholder("Dataset placeholder2")
.build();
// Get a template ARN to use.
String arn = getTemplateARN(qsClient, account, dataSetArn, analysisArn);
DashboardSourceTemplate sourceTemplate = DashboardSourceTemplate.builder()
.dataSetReferences(dataSetReference)
.arn(arn)
.build();
DashboardSourceEntity sourceEntity = DashboardSourceEntity.builder()
.sourceTemplate(sourceTemplate)
.build();
UpdateDashboardRequest dashboardRequest = UpdateDashboardRequest.builder()
.awsAccountId(account)
.dashboardId(dashboardId)
.name("UpdateTest")
.sourceEntity(sourceEntity)
.themeArn("arn:aws:quicksight::aws:theme/SEASIDE")
.build();
UpdateDashboardResponse response = qsClient.updateDashboard(dashboardRequest);
System.out.println("Dashboard " + response.dashboardId() + " has been updated");
} catch (QuickSightException e) {
System.err.println(e.awsErrorDetails().errorMessage());
System.exit(1);
}
}
private static String getTemplateARN(QuickSightClient qsClient, String account, String dataset, String analysisArn) {
String arn = "";
try {
DataSetReference setReference = DataSetReference.builder()
.dataSetArn(dataset)
.dataSetPlaceholder("Dataset placeholder2")
.build();
TemplateSourceAnalysis templateSourceAnalysis = TemplateSourceAnalysis.builder()
.dataSetReferences(setReference)
.arn(analysisArn)
.build();
TemplateSourceEntity sourceEntity = TemplateSourceEntity.builder()
.sourceAnalysis(templateSourceAnalysis)
.build();
CreateTemplateRequest createTemplateRequest = CreateTemplateRequest.builder()
.awsAccountId(account)
.name("NewTemplate")
.sourceEntity(sourceEntity)
.templateId("a9a277fb-7239-4890-bc7a-8a3e82d67a37") // Specify a GUID value
.build();
CreateTemplateResponse response = qsClient.createTemplate(createTemplateRequest);
arn = response.arn();
} catch (QuickSightException e) {
System.err.println(e.awsErrorDetails().errorMessage());
System.exit(1);
}
return arn;
}
}

How to prevent < converted to < when using web service proxy class in JDeveloper

I am calling a BPM web service that sends HTML email. I generated a web service proxy in JDeveloper 11.1.1.7. The type of the body of the email is xsd:string which should map to java String. I understand that certain characters, for example < > &, are reserved and converted during the xml document creation during the proxy operation.
Using SOAPUI to call the service, I can pass the body as <h1>My Heading</h1> and service responds correctly, sending the email with HTML as expected. When doing the same from a POJO that calls the proxy, <h1> is converted to <h1>My heading</h1>.
I have tried passing the body as a CDATA section but this makes no difference. I have tried converting the body to bytes then back to a UTF-8 string before the call but still no difference. I have access to the BPM service code. Is there a way I can send html to the service from a proxy, that retains the special characters?
I figured this out finally. While the JDeveloper web service proxy generator is useful most of the time, in this case it was not since I needed to send xml special characters to the service. Perhaps there is a way to manipulate the proxy code to do what you want but I couldn't figure it out.
Of particular help was this AMIS blog entry. And if you ever need to handle special characters during JAXB marshalling, this entry will help you too. A great summary of the steps to use the java URLConnection class is here and that answer points to a library that would probably make life even easier.
So here is the raw wrapper code below. The particular BPM email service we wrote also writes to a log and that explains the complex types in the raw xml input. Naturally I will populate the email values from a passed in POJO object in the main sendMail wrapper method.
package com.yourdomain.sendmail.methods;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.HttpURLConnection;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLConnection;
import java.util.List;
import java.util.Map;
import java.util.Scanner;
import oracle.adf.model.connection.url.URLConnectionProxy;
import oracle.adf.share.ADFContext;
public class SendMailWrapper {
public SendMailWrapper() {
super();
}
public static void main(String[] args) throws MalformedURLException, IOException {
SendMailWrapper w = new SendMailWrapper();
w.sendMail();
}
public void sendMail() throws MalformedURLException, IOException {
String xmlInput =
"<soapenv:Envelope xmlns:soapenv=\"http://schemas.xmlsoap.org/soap/envelope/\" " +
"xmlns:sen=\"http://xmlns.oracle.com/bpmn/bpmnProcess/SendEmailProcess\" " +
"xmlns:ema=\"http://www.wft.com/BPM/SendEmail/Email\">\n" +
"<soapenv:Header/>" +
"<soapenv:Body>\n" +
"<sen:start>\n" +
"<ema:emailInput>\n" +
"<ema:emailContent>\n" +
"<ema:toAddr>your.name#yourdomain.com</ema:toAddr>\n" +
"<ema:fromAddr></ema:fromAddr>\n" +
"<ema:ccAddr></ema:ccAddr>\n" +
"<ema:bccAddr></ema:bccAddr>\n" +
"<ema:subject>SendMail HTML</ema:subject>\n" +
"<ema:body><h1>My Heading</h1><p>Text</p></ema:body>\n" +
"<ema:contentType>text/html</ema:contentType>\n" +
"</ema:emailContent>\n" +
"<ema:emailHistory>\n" +
"<ema:projectName>Soap Test</ema:projectName>\n" +
"<ema:reqID></ema:reqID>\n" +
"<ema:compositeID></ema:compositeID>\n" +
"<ema:processID></ema:processID>\n" +
"<ema:processName></ema:processName>\n" +
"<ema:activityName></ema:activityName>\n" +
"<ema:insertDate></ema:insertDate>\n" +
"<ema:insertByID></ema:insertByID>\n" +
"<ema:insertByName></ema:insertByName>\n" +
"<ema:commentType></ema:commentType>\n" +
"<ema:commentInfo></ema:commentInfo>\n" +
"</ema:emailHistory>\n" +
"</ema:emailInput>\n" +
"</sen:start>\n" +
"</soapenv:Body>\n" +
"</soapenv:Envelope>\n";
System.out.println(xmlInput);
String wsURL = getWsdlUrl();
URL url = new URL(wsURL);
URLConnection connection = url.openConnection();
HttpURLConnection httpConn = (HttpURLConnection)connection;
ByteArrayOutputStream bout = new ByteArrayOutputStream();
byte[] buffer = new byte[xmlInput.length()];
buffer = xmlInput.getBytes();
bout.write(buffer);
byte[] b = bout.toByteArray();
String SOAPAction = "start"; //this is the method in the service
httpConn.setRequestProperty("Content-Length", String.valueOf(b.length));
httpConn.setRequestProperty("Content-Type", "text/xml;charset=UTF-8");
//some other props available but don't need to be set...
//httpConn.setRequestProperty("Accept-Encoding", "gzip,deflate");
//httpConn.setRequestProperty("Host", "your.host.com:80");
//httpConn.setRequestProperty("Connection", "Keep-Alive");
//httpConn.setRequestProperty("User-Agent", "Apache-HttpClient/4.1.1 (java 1.5)");
httpConn.setRequestProperty("SOAPAction", SOAPAction);
httpConn.setRequestMethod("POST");
httpConn.setDoOutput(true);
httpConn.setDoInput(true);
OutputStream out = httpConn.getOutputStream();
out.write(b);
out.close();
//check response code...
int status = httpConn.getResponseCode();
String respMessage = httpConn.getResponseMessage();
System.out.println("RESPONSE CODE: " + status + " RESPONSE MESSAGE: " + respMessage);
//check response headers...
for (Map.Entry<String, List<String>> header : connection.getHeaderFields().entrySet()) {
System.out.println(header.getKey() + "=" + header.getValue());
}
//check error stream - this helps alot when debugging...
InputStream errorStream = ((HttpURLConnection)connection).getErrorStream();
if (errorStream != null) {
System.out.println("Error Stream: " + convertStreamToString(errorStream));
}
//if there was an expected response, you need to parse it...
/* String responseString = "";
String outputString = "";
InputStreamReader isr = new InputStreamReader(httpConn.getInputStream());
BufferedReader in = new BufferedReader(isr);
while ((responseString = in.readLine()) != null) {
outputString = outputString + responseString;
}
isr.close();
System.out.println("OUT: " + outputString); */
}
static String convertStreamToString(InputStream is) {
Scanner s = new Scanner(is).useDelimiter("\\A");
return s.hasNext() ? s.next() : "";
}
private static String getWsdlUrl() {
String result = null;
try {
URLConnectionProxy wsConnection = (URLConnectionProxy)ADFContext.getCurrent().getConnectionsContext().lookup("SendMailProxyConnection");
result = wsConnection.getURL().toExternalForm();
} catch (Exception e) {
e.printStackTrace();
}
return result;
}
}
Happy coding.