I want to get a list of protobuf message objects from the Spring boot app.
I did manage to get a single protobuf message object from the app but getting a list of them throws exception.
...
2020-01-24 14:57:02.359 ERROR 15883 --- [nio-8081-exec-1] o.a.c.c.C.[.[.[/].[dispatcherServlet] : Servlet.service() for servlet [dispatcherServlet] in context with path [] threw exception [Request processing failed; nested exception is org.springframework.http.converter.HttpMessageConversionException: Type definition error: [simple type, class com.google.protobuf.UnknownFieldSet$Parser]; nested exception is com.fasterxml.jackson.databind.exc.InvalidDefinitionException: No serializer found for class com.google.protobuf.UnknownFieldSet$Parser and no properties discovered to create BeanSerializer (to avoid exception, disable SerializationFeature.FAIL_ON_EMPTY_BEANS) (through reference chain: java.util.ImmutableCollections$ListN[0]->com.example.demo.Lecture["unknownFields"]->com.google.protobuf.UnknownFieldSet["parserForType"])] with root cause
com.fasterxml.jackson.databind.exc.InvalidDefinitionException: No serializer found for class com.google.protobuf.UnknownFieldSet$Parser and no properties discovered to create BeanSerializer (to avoid exception, disable SerializationFeature.FAIL_ON_EMPTY_BEANS) (through reference chain: java.util.ImmutableCollections$ListN[0]->com.example.demo.Lecture["unknownFields"]->com.google.protobuf.UnknownFieldSet["parserForType"])
at com.fasterxml.jackson.databind.exc.InvalidDefinitionException.from(InvalidDefinitionException.java:77) ~[jackson-databind-2.10.2.jar:2.10.2]
...
My code (simplified).
tl;dr
create Spring boot app
generate class from proto file
try return List of generated class objects (RESTful)
My code (simplified).
Controler
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.*;
import java.util.List;
#Slf4j
#org.springframework.web.bind.annotation.RestController
#RequestMapping("/timetable")
public class RestController {
#PostMapping("/single") // Works
private Lecture getLecture(#RequestBody Lecture lecture) {
log.info("Single2 got: {}", lecture);
return Lecture.newBuilder(lecture)
.setDuration(lecture.getDuration() +1)
.build();
}
#GetMapping("/list") // Does not work
private #ResponseBody List<Lecture> getLectures() {
return List.of(
Lecture.newBuilder()
.setDuration(1)
.setWeekDay(Lecture.WeekDay.MONDAY)
.setModule(Module.newBuilder().setName("Math1").build())
.build()
// ...
);
}
}
App
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Primary;
import org.springframework.http.converter.protobuf.ProtobufHttpMessageConverter;
import org.springframework.http.converter.protobuf.ProtobufJsonFormatHttpMessageConverter;
#SpringBootApplication
public class DemoApplication {
public static void main(String[] args) {
SpringApplication.run(DemoApplication.class, args);
}
#Bean
#Primary
ProtobufHttpMessageConverter protobufHttpMessageConverter() {
return new ProtobufJsonFormatHttpMessageConverter();
}
}
pom.xml
<!-- ... -->
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<java.version>11</java.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<!-- https://dzone.com/articles/exposing-microservices-over-rest-protocol-buffers-->
<dependency>
<groupId>com.google.protobuf</groupId>
<artifactId>protobuf-java</artifactId>
<version>3.11.1</version>
</dependency>
<dependency>
<groupId>com.googlecode.protobuf-java-format</groupId>
<artifactId>protobuf-java-format</artifactId>
<version>1.4</version>
</dependency>
<!-- https://mvnrepository.com/artifact/com.google.protobuf/protobuf-java-util -->
<dependency>
<groupId>com.google.protobuf</groupId>
<artifactId>protobuf-java-util</artifactId>
<version>3.11.1</version>
</dependency>
</dependencies>
<!-- ... -->
I generate message objects using:
#!/bin/bash
SRC_DIR=../proto
DST_DIR=../../../target/
mkdir -p $DST_DIR
protoc -I=$SRC_DIR --java_out=$DST_DIR $SRC_DIR/college.proto
proto file
syntax = "proto3";
package my.college;
option java_multiple_files = true;
option java_package = "com.example.demo";
message Module {
string name = 1;
// ... other
}
message Lecture {
WeekDay weekDay = 1;
Module module = 2;
uint32 duration = 3;
// ... other
enum WeekDay {
SUNDAY = 0;
MONDAY = 1;
// ...
}
}
I did found simmilar issue but it had no solution.
Explanation
Spring will choose the HttpMessageConverter that matches the appropriate converter for the object type of your response body. In this case, it is likely choosing MappingJackson2HttpMessageConverter of the ProtobufJsonFormatHttpMessageConverter because your response body has type List.
MappingJackson2HttpMessageConverter implements HttpMessageConverter<Object>
ProtobufJsonFormatHttpMessageConverter implements HttpMessageConverter<Message>
Since ProtobufJsonFormatHttpMessageConverter does not support serializing a List type, we can instead tell the MappingJackson2HttpMessageConverter how to serialize the Message type through configuration.
Solution
A bean of type Jackson2ObjectMapperBuilderCustomizer can be used to register a serializer for Message types.
#Bean
public Jackson2ObjectMapperBuilderCustomizer jackson2ObjectMapperBuilderCustomizer() {
return o -> o.serializerByType(Message.class, new JsonSerializer<Message>() {
#Override
public void serialize(Message value, JsonGenerator gen, SerializerProvider serializers) throws IOException {
gen.writeRawValue(JsonFormat.printer().print(value));
}
});
}
Workaround
I couldn't find a solution to the problem so came up with a workaround.
Instead of returning generated protobuf message objects I returned wrappers for those objects. Using Lombok annotation it could be done:
import lombok.Data;
#Data // Lombok magic
public class Module {
private String name;
// ...
public Module(ie.gmit.proto.Module moduleProto){
this.name = moduleProto.getName();
// ...
}
}
This workaround doesn't feel very bad as it uses standard Spring boot dependencies.
Related
I want to send email using thymeleaf for the email content, but it seems it cannot find the html template inside WEB-INF/templates/notifications.
I am using thymeleaf spring 5 on spring boot. Spring boot (2.0.2.RELEASE) and Thymeleaf-spring5 (3.0.11.RELEASE)
Below are my configuration/changes:
POM
...
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.0.2.RELEASE</version>
<relativePath /> <!-- lookup parent from repository -->
</parent>
...
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
<dependency>
<groupId>org.thymeleaf.extras</groupId>
<artifactId>thymeleaf-extras-springsecurity4</artifactId>
</dependency>
<dependency>
<groupId>org.thymeleaf</groupId>
<artifactId>thymeleaf-spring5</artifactId>
<version>3.0.11.RELEASE</version>
</dependency>
...
NotificationTemplateConfiguration
#Configuration
public class NotificationTemplateConfiguration {
#Bean
#Qualifier(value = "myTemplateEngine")
public SpringTemplateEngine springTemplateEngine() {
SpringTemplateEngine templateEngine = new SpringTemplateEngine();
templateEngine.addTemplateResolver(myTemplateResolver());
return templateEngine;
}
#Bean
public SpringResourceTemplateResolver myTemplateResolver(){
SpringResourceTemplateResolver myTemplateResolver = new SpringResourceTemplateResolver();
myTemplateResolver.setPrefix("WEB-INF/templates/notifications/");
myTemplateResolver.setSuffix(".html");
myTemplateResolver.setTemplateMode(TemplateMode.HTML);
myTemplateResolver.setCharacterEncoding(StandardCharsets.UTF_8.name());
// I tried adding these lines but still it does not work
// myTemplateResolver.setOrder(0);
// myTemplateResolver.setCheckExistence(true);
return myTemplateResolver;
}
}
EmailService
#Component
public class EmailService {
...
#Autowired
#Qualifier(value = "myTemplateEngine")
private SpringTemplateEngine m_myTemplateEngine;
...
private String buildContent() {
final Context context = new Context();
context.setVariable("recvName", getRecvName());
context.setVariable("inquiry", getInquiry());
context.setVariable("logoImageUrl", getLogoImageUrl());
// This is causing the error as it cannot find `WEB-INF/templates/notifications/inquiry-notification.html`, but this file really exists
return m_templateEngine.process("inquiry-notification", context);
}
public void sendEmail() throws MessagingException {
MimeMessage message = m_javaMailSender.createMimeMessage();
MimeMessageHelper helper = new MimeMessageHelper(message, true, ConstantUtil.CHARACTER_ENCODING);
String content = buildContent();
helper.setFrom(getFromEmail());
helper.setReplyTo(getNoReplyEmail());
helper.setText(content, true);
helper.setSubject(getSubject());
helper.setTo(getRecipientEmail());
m_javaMailSender.send(message);
}
}
Now, I am getting an error on buildContent():
Caused by: java.io.FileNotFoundException: class path resource [WEB-INF/templates/notifications/inquiry-notification.html] cannot be opened because it does not exist
at org.springframework.core.io.ClassPathResource.getInputStream(ClassPathResource.java:180)
at org.thymeleaf.spring5.templateresource.SpringResourceTemplateResource.reader(SpringResourceTemplateResource.java:103)
at org.thymeleaf.templateparser.markup.AbstractMarkupTemplateParser.parse(AbstractMarkupTemplateParser.java:223)
... 16 common frames omitted
Problem
The file WEB-INF/templates/notifications/inquiry-notification.html is existing and it is inside WEB-INF/templates/notifications even if I check the war file. The problem is on m_templateEngine.process("inquiry-notification", context) as it cannot find inquiry-notification even if it exists. If I comment out this and just return a hard coded string (for testing only)... it will send the email without any error.
This WEB-INF/templates/notifications/inquiry-notification.html really exists, but I am out of idea on why it cannot find it.
Any idea on why it cannot find the file inside WEB-INF and how to fix it?
Update:
If I change the prefix into:
myTemplateResolver.setPrefix("classpath:/templates/notifications/");
and move the folder from WEB-INF/templates/notifications/ into resources/templates/notifications.
Everything works, but I want to use WEB-INF/templates/notifications/ and not resources/templates/notifications.
As what #Ralph commented, it can be seen in the exception that it is reading from class path resource and not context resource.
My problem now is how can I make it read from context resource (WEB-INF/templates/notifications) and not from class path resource (resources/templates/notifications).
I've written a basic spring boot service that consumes some data via rest API and publishes it to rabbitmq and kafka.
To test the service class handling kafka producing, I followed this guide: https://www.baeldung.com/spring-boot-kafka-testing
In isolation, the test (KafkaMessagingServiceImplTest) works perfectly both in intellij idea and via mvn on the command line. Running all project tests in idea works fine. However, when I run all project tests via maven on the command line, this test fails with an NPE when trying to make the assertion on the payload String.
I've narrowed down the location of the root problem to another test class (AppPropertiesTest) which is solely testing my AppProperties component (which is a component I use to pull config from application.properties in a tidy way). When, and only when, the tests within that test class are run alongside the failing test using 'mvn clean install' in project root, does the NPE show up. Commenting out the tests in this class or annotating it with #DirtiesContext fixes the problem. Apparently something loaded into the spring context by this test class causes an issue with the timing/order of events/countdownlatch in the other test. Of course, I don't want to use #DirtiesContext as it can lead to a much slower build as the project increases in complexity. It also does not explain the problem.. and I can't handle that :)
AppPropertiesTest uses constructor injection to inject the AppProperties component. It also extends a abstract class 'GenericServiceTest' which is annotated by:
#SpringBootTest
#TestConstructor(autowireMode = TestConstructor.AutowireMode.ALL)
and contains nothing else. As you probably know, the SpringBootTest annotation builds a test spring context and wires in boilerplate to allow effective testing of a spring app's dependency injection etc. and the TestConstructor annotation allows constructor injection in some of my tests. FWIW, I have tried removing the TestConstructor annotation and using plain old Autowiring in the AppProperties class to see if it makes a difference but it does not.
The failing test class also extends GenericServiceTest, as it requires the spring context to inject some of the dependencies such as the consumer and the messaging service being tested and AppProperties instance within etc.
So I know where the problem lies but I don't know what the problem is. Even when the test fails with the NPE, I can see in the logs that the consumer has successfully consumed the message before the failure, as per the Baeldung guide :
TestKafkaConsumer : received payload='ConsumerRecord(topic = test-kafka-topic, partition = 0, leaderEpoch = 0, offset = 0, CreateTime = 1618997289238, serialized key size = -1, serialized value size = 43, headers = RecordHeaders(headers = [], isReadOnly = false), key = null, value = This is a test message to be sent to Kafka.)'
However, the payLoad is null when we get back to the assertion. I've tried all kinds of things like Thread.sleep() in the failing test to give it more time and I've increased the await() timeout but no joy.
I find it bizarre that the tests are fine in IDEA and in isolation. Now it's starting to drive me a little crazy and I can't debug it because the problem doesn't occur in my IDE.
If anyone has any ideas, it would be greatly appreciated!
Thanks.
EDIT: Someone very reasonably suggested that I add some code so here goes :)
The Failing Test (fails at assertTrue(payload.contains(testMessage)) because payLoad is null). The autowired kafkaMessagingService simply has the dependencies of AppProperties and KakfaTemplate injected and calls kafkaTemplate.send():
#EmbeddedKafka(partitions = 1, brokerProperties = { "listeners=PLAINTEXT://localhost:9092", "port=9092" })
class KafkaMessagingServiceImplTest extends GenericServiceTest {
#Autowired
#Qualifier("kafkaMessagingServiceImpl")
private IMessagingService messagingService;
#Autowired
private TestKafkaConsumer kafkaConsumer;
#Value("${app.topicName}")
private String testTopic;
#Test
public void testSendAndConsumeKafkaMessage() throws InterruptedException {
String testMessage = "This is a test message to be sent to Kafka.";
messagingService.sendMessage(testMessage);
kafkaConsumer.getLatch().await(2000, TimeUnit.MILLISECONDS);
String payload = kafkaConsumer.getPayload();
assertTrue(payload.contains(testMessage));
}
The TestConsumer (used to consume in the test above)
#Component
public class TestKafkaConsumer {
private static final Logger LOGGER = LoggerFactory.getLogger(TestKafkaConsumer.class);
private CountDownLatch latch = new CountDownLatch(1);
private String payload = null;
#KafkaListener(topics = "${app.topicName}")
public void receive(ConsumerRecord<?, ?> consumerRecord) {
LOGGER.info("received payload='{}'", consumerRecord.toString());
setPayload(consumerRecord.toString());
latch.countDown();
}
public CountDownLatch getLatch() {
return latch;
}
public String getPayload() {
return payload;
}
public void setPayload(String payload) {
this.payload = payload;
}
Project dependencies:
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-amqp</artifactId>
<version>2.2.2.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework.kafka</groupId>
<artifactId>spring-kafka</artifactId>
<version>2.5.8.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-configuration-processor</artifactId>
<optional>true</optional>
</dependency>
<!-- https://mvnrepository.com/artifact/org.mockito/mockito-all -->
<dependency>
<groupId>org.mockito</groupId>
<artifactId>mockito-all</artifactId>
<version>1.10.19</version>
<scope>test</scope>
</dependency>
<!-- https://mvnrepository.com/artifact/org.springframework.kafka/spring-kafka-test -->
<dependency>
<groupId>org.springframework.kafka</groupId>
<artifactId>spring-kafka-test</artifactId>
<version>2.5.6.RELEASE</version>
<scope>test</scope>
</dependency>
</dependencies>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>${spring-cloud.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
AppPropertiesTest class (the context of which seems to cause the problem)
class AppPropertiesTest extends GenericServiceTest {
private final AppProperties appProperties;
public AppPropertiesTest(AppProperties appProperties) {
this.appProperties = appProperties;
}
#Test
public void testAppPropertiesGetQueueName() {
String expected = "test-queue";
String result = appProperties.getRabbitMQQueueName();
assertEquals(expected, result);
}
#Test
public void testAppPropertiesGetDurableQueue() {
boolean isDurableQueue = appProperties.isDurableQueue();
assertTrue(isDurableQueue);
}
}
The AppProperties class that the AppPropertiesTest class is testing:
#Component
#ConfigurationProperties("app")
public class AppProperties {
// a whole bunch of properties by name that are prefixed by app. in the application.properties file. Nothing else
}
The Generic service test class which both tests extend.
#SpringBootTest
#TestConstructor(autowireMode = TestConstructor.AutowireMode.ALL)
public abstract class GenericServiceTest {
}
The failure (you can see on the line above the payload has been received and printed out).
2021-04-21 14:15:07.113 INFO 493384 --- [ntainer#0-0-C-1] service.TestKafkaConsumer : received payload='ConsumerRecord(topic = test-kafka-topic, partition = 0, leaderEpoch = 0, offset = 0, CreateTime = 1619010907076, serialized key size = -1, serialized value size = 43, headers = RecordHeaders(headers = [], isReadOnly = false), key = null, value = This is a test message to be sent to Kafka.)'
[ERROR] Tests run: 1, Failures: 0, Errors: 1, Skipped: 0, Time elapsed: 3.791 s <<< FAILURE! - in
service.KafkaMessagingServiceImplTest
[ERROR] testSendAndConsumeKafkaMessage Time elapsed: 2.044 s <<< ERROR!
java.lang.NullPointerException
at service.KafkaMessagingServiceImplTest.testSendAndConsumeKafkaMessage(KafkaMessagingServiceImplTest.java:42)
The problem is that TestListener is a #Component so it is being added twice - the record is going to the other instance.
I added more debugging to verify the getter is called on a different instance.
#Component
public class TestKafkaConsumer {
private static final Logger LOGGER = LoggerFactory.getLogger(TestKafkaConsumer.class);
private final CountDownLatch latch = new CountDownLatch(1);
private String payload = null;
#KafkaListener(id = "myListener", topics = "${app.kafkaTopicName}")
public void receive(ConsumerRecord<?, ?> consumerRecord) {
LOGGER.info("received payload='{}'", consumerRecord.toString());
setPayload(consumerRecord.toString());
if (payload != null) {
LOGGER.info(this + ": payload is not null still");
}
latch.countDown();
if (payload != null) {
LOGGER.info(this + ": payload is not null after latch countdown");
}
}
public CountDownLatch getLatch() {
return latch;
}
public String getPayload() {
LOGGER.info(this + ": getting Payload");
return payload;
}
public void setPayload(String payload) {
this.payload = payload;
}
}
If you don't want to use #DirtiesContext, you can at least stop the listener containers after the tests complete:
#SpringBootTest
#TestConstructor(autowireMode = TestConstructor.AutowireMode.ALL)
public abstract class GenericDataServiceTest {
#AfterAll
static void stopContainers(#Autowired KafkaListenerEndpointRegistry registry) {
registry.stop();
}
}
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------
I'm getting the following exception when using cosmosdb sdk for Java:
Exception in thread "main" java.lang.NoSuchFieldError: ALLOW_TRAILING_COMMA
at com.microsoft.azure.cosmosdb.internal.Utils.<clinit>(Utils.java:75)
at com.microsoft.azure.cosmosdb.rx.internal.RxDocumentClientImpl.<clinit>(RxDocumentClientImpl.java:132)
at com.microsoft.azure.cosmosdb.rx.AsyncDocumentClient$Builder.build(AsyncDocumentClient.java:224)
at Program2.<init>(Program2.java:25)
at Program2.main(Program2.java:30)
I'm just trying to connect to the CosmosDB using AsyncDocumentClient. The exception occurs in that moment.
executorService = Executors.newFixedThreadPool(100);
scheduler = Schedulers.from(executorService);
client = new AsyncDocumentClient.Builder()
.withServiceEndpoint("[cosmosurl]")
.withMasterKeyOrResourceToken("[mykey]")
.withConnectionPolicy(ConnectionPolicy.GetDefault())
.withConsistencyLevel(ConsistencyLevel.Eventual)
.build();
I heard about some library conflict but I haven't found the properly fix.
Thanks!
Please refer to my working sample.
Java code:
import com.microsoft.azure.cosmosdb.ConnectionPolicy;
import com.microsoft.azure.cosmosdb.ConsistencyLevel;
import com.microsoft.azure.cosmosdb.rx.AsyncDocumentClient;
public class test {
public static void main(String[] args) throws Exception {
AsyncDocumentClient client = new AsyncDocumentClient.Builder()
.withServiceEndpoint("https://XXX.documents.azure.com:443/")
.withMasterKeyOrResourceToken("XXXX")
.withConnectionPolicy(ConnectionPolicy.GetDefault())
.withConsistencyLevel(ConsistencyLevel.Eventual)
.build();
System.out.println(client);
}
}
pom.xml
<dependencies>
<dependency>
<groupId>com.microsoft.azure</groupId>
<artifactId>azure-cosmosdb</artifactId>
<version>2.6.4</version>
</dependency>
<dependency>
<groupId>com.microsoft.azure</groupId>
<artifactId>azure-cosmosdb-commons</artifactId>
<version>2.6.4</version>
</dependency>
</dependencies>
I am new to AWS and I am currently trying to understand Lambda functions and to trigger it when I upload file to S3 bucket.
I wrote a handler class for this:
public class Hello implements RequestHandler<Employee, String> {
public String handleRequest(Employee input, Context context) {
context.getLogger().log("helloWorld");
return "Hello World " ;
}
}
This was just a basic and I could see the "helloworld" printed in logs in CloudWatch when I upload a file to S3 bucket.
But Now what I want to log the metadata of the file (fileName, createdTime etc.).
I went thru a sample template example in AWS Lambda page, where I can see using Nodejs, we have the event as the argument and we can extract the name and other fields using this field.
const aws = require('aws-sdk');
const s3 = new aws.S3({ apiVersion: '2006-03-01' });
exports.handler = (event, context, callback) => {
const bucket = event.Records[0].s3.bucket.name;
...
}
But as a Java developer, I tried to use S3EventNotification as the argument:
public class Hello implements RequestHandler<S3EventNotification, String> {
public String handleRequest(S3EventNotification input, Context context) {
context.getLogger().log(input.getRecords().get(0).getEventSource());
return "Hello World " ;
}
}
But I am getting below error:
An error occurred during JSON parsing: java.lang.RuntimeException
java.lang.RuntimeException: An error occurred during JSON parsing
Caused by: lambdainternal.util.ReflectUtil$ReflectException: java.lang.NoSuchMethodException: com.amazonaws.services.s3.event.S3EventNotification$S3ObjectEntity.<init>(java.lang.String, java.lang.Long, java.lang.String, java.lang.String)
Caused by: java.lang.NoSuchMethodException: com.amazonaws.services.s3.event.S3EventNotification$S3ObjectEntity.<init>(java.lang.String, java.lang.Long, java.lang.String, java.lang.String)
How can I achieve the same thing in Java? Thanks.
Try some variant of the following:
import com.amazonaws.services.lambda.runtime.Context;
import com.amazonaws.services.lambda.runtime.RequestHandler;
import com.amazonaws.services.lambda.runtime.events.S3Event;
public class Hello implements RequestHandler<S3Event, Void> {
#Override
public Void handleRequest(S3Event s3event, Context context) {
try {
S3EventNotificationRecord record = s3event.getRecords().get(0);
String bkt = record.getS3().getBucket().getName();
String key = record.getS3().getObject().getKey().replace('+', ' ');
key = URLDecoder.decode(key, "UTF-8");
} catch (Exception e) {
// do something
}
return null;
}
}
And here are the corresponding dependencies that I used in pom.xml:
<dependency>
<groupId>com.amazonaws</groupId>
<artifactId>aws-java-sdk-s3</artifactId>
<version>1.11.228</version>
</dependency>
<dependency>
<groupId>com.amazonaws</groupId>
<artifactId>aws-lambda-java-core</artifactId>
<version>1.1.0</version>
</dependency>
<dependency>
<groupId>com.amazonaws</groupId>
<artifactId>aws-lambda-java-events</artifactId>
<version>2.0.1</version>
</dependency>
And here is the build specification from my pom.xml (which will cause dependent classes to be pulled into my built JAR):
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-shade-plugin</artifactId>
<version>2.3</version>
<configuration>
<createDependencyReducedPom>false</createDependencyReducedPom>
</configuration>
<executions>
<execution>
<phase>package</phase>
<goals>
<goal>shade</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
None of this is very simple, unfortunately, but that's Java and Maven for you. AWS Lambda programming in Node.js or Python is much simpler (and more fun) than in Java, so if there's no strong requirement to write it in Java, you're better off not writing in Java.
Also note that if the Lambda is going to be invoked asynchronously then the output type should be Void rather than String (see docs).
EDITS AT THE BOTTOM
Currently experiencing one error after another. It seems I make a step forward and have to take 2 steps back :) Unfortunately I have nobody locally that I can pair with so a lot of my debugging is being done over Google and SO.
I'm not very familiar with jaxb and using it to make soap calls (which is what I'm currently trying to do - connect to soap service) but I was told that this is the easiest approach for what I'm wanting to do and that I should look into this since I'm using spring-boot in the rest of the project so I found a tutorial (here) and started there.
Here is the plugin section of my pom that creates the package of classes based on the wsdl:
<plugin>
<groupId>org.jvnet.jaxb2.maven2</groupId>
<artifactId>maven-jaxb2-plugin</artifactId>
<version>0.12.3</version>
<executions>
<execution>
<goals>
<goal>generate</goal>
</goals>
</execution>
</executions>
<configuration>
<schemaLanguage>WSDL</schemaLanguage>
<generatePackage>vantiveGenericWebService.wsdl</generatePackage>
<schemas>
<schema>
<url>http://hostname:port/GenericWebService/ws?service=InstalledComponentService2&appl=vantive&wsdl</url>
</schema>
</schemas>
</configuration>
</plugin>
I'm able to create the wsdl classes fine.
I created a client class:
import org.apache.log4j.Logger;
import org.springframework.stereotype.Component;
import org.springframework.ws.client.core.support.WebServiceGatewaySupport;
import vantiveGenericWebService.wsdl.GetICDetailsByAssetTag;
import vantiveGenericWebService.wsdl.GetICDetailsByAssetTagResponse;
#Component
public class VantiveGenericWebServiceClient extends WebServiceGatewaySupport {
Logger log = Logger.getLogger(VantiveGenericWebServiceClient.class.getName());
public GetICDetailsByAssetTagResponse getICDetailsByAssetTag(String assetTag) {
GetICDetailsByAssetTag request = new GetICDetailsByAssetTag();
request.setAssetTag(assetTag);
log.info("Requesting asset tag for: " + assetTag);
GetICDetailsByAssetTagResponse response = (GetICDetailsByAssetTagResponse) getWebServiceTemplate().marshalSendAndReceive("http://hostname:port/GenericWebService/ws?service=InstalledComponentService2&appl=vantive", request);
return response;
}
}
as well as a client config class:
#Configuration
public class VantiveGenericWebServiceConfig {
#Bean
public Jaxb2Marshaller marshaller() {
Jaxb2Marshaller marshaller = new Jaxb2Marshaller();
marshaller.setContextPath("vantiveGenericWebService.wsdl");
return marshaller;
}
#Bean
public VantiveGenericWebServiceClient vantiveGenericWebServiceClient(Jaxb2Marshaller marshaller) {
VantiveGenericWebServiceClient client = new VantiveGenericWebServiceClient();
client.setDefaultUri("http://hostname:port/GenericWebService/ws?&service=InstalledComponentService2&appl=vantive");
client.setMarshaller(marshaller);
client.setUnmarshaller(marshaller);
// ClientInterceptor[] interceptors = new ClientInterceptor[] { new ClientInterceptor() {
// #Override
// public boolean handleRequest(MessageContext messageContext) throws WebServiceClientException {
// return false;
// }
//
// #Override
// public boolean handleResponse(MessageContext messageContext) throws WebServiceClientException {
// return true;
// }
//
// #Override
// public boolean handleFault(MessageContext messageContext) throws WebServiceClientException {
// return true;
// }
//
// #Override
// public void afterCompletion(MessageContext messageContext, Exception e) throws WebServiceClientException {
//
// }
// }};
// client.setInterceptors(interceptors);
return client;
}
}
Now on to the error! First I was getting one that looked like this:
org.springframework.oxm.MarshallingFailureException: JAXB marshalling exception; nested exception is javax.xml.bind.MarshalException
- with linked exception:
[com.sun.istack.internal.SAXException2: unable to marshal type "vantiveGenericWebService.wsdl.GetICDetailsByAssetTag" as an element because it is missing an #XmlRootElement annotation]
so then I modified the GetICDetailsByAssetTag class (a class that was generated from the wsdl) to look like this:
#XmlRootElement(name = "assetTag")
#XmlAccessorType(XmlAccessType.FIELD)
#XmlType(name = "getICDetailsByAssetTag", propOrder = {
"assetTag"
})
public class GetICDetailsByAssetTag {
which I'm not 100% sure is the correct xml root but the error goes away. Now my 2nd (and current) error is this:
org.springframework.oxm.UnmarshallingFailureException: JAXB unmarshalling exception; nested exception is javax.xml.bind.UnmarshalException: unexpected element (uri:"http://schemas.xmlsoap.org/soap/envelope/", local:"Fault"). Expected elements are <{http://hostname/vantive/ws/InstalledComponentService2}DuplicateException>,<{http://hostname/vantive/ws/InstalledComponentService2}FunctionalException>,<{http://hostname/vantive/ws/InstalledComponentService2}GetICDetailsByAssetTagResponse>,<{http://hostname/vantive/ws/InstalledComponentService2}GetInstalledComponentDetailsByAssetTagAndStatusResponse>,<{http://hostname/vantive/ws/InstalledComponentService2}InvalidValuesException>,<{http://hostname/vantive/ws/InstalledComponentService2}SetClientDeviceXrefResponse>,<{http://hostname/vantive/ws/InstalledComponentService2}SetInstalledComponentAssetTagResponse>,<{http://hostname/vantive/ws/InstalledComponentService2}TechnicalException>,<{http://hostname/vantive/ws/InstalledComponentService2}assetTag>,<{http://hostname/vantive/ws/InstalledComponentService2}getICDetailsByAssetTag>,<{http://hostname/vantive/ws/InstalledComponentService2}getInstalledComponentDetailsByAssetTagAndStatus>,<{http://hostname/vantive/ws/InstalledComponentService2}installedComponentAssetTagDetails>,<{http://hostname/vantive/ws/InstalledComponentService2}installedComponentDetails>,<{http://hostname/vantive/ws/InstalledComponentService2}setClientDeviceXref>,<{http://com.savvis.it/vantive/ws/InstalledComponentService2}setClientDeviceXrefResult>,<{http://hostname/vantive/ws/InstalledComponentService2}setInstalledComponentAssetTag>,<{http://hostname/vantive/ws/InstalledComponentService2}setInstalledComponentAssetTagResult>
I can post the full stacktraces if necessary. I'm omitting right now for space.
Believe me when I say I have scoured Google and SO looking at all the posts and suggested answers for these errors (that's why I have the currently commented out interceptor code in my config) and I just can't seem to find anything that 1. fixes it and 2. explains what exactly I'm doing wrong. I don't very much care for having something that works but I also where can't tell you what's happening.
Coincidentally, the interceptor portion makes it so no error is thrown but then my response is null.
I have a feeling that my problem is going to be a simple one but since this is my first time doing something like this it's just not jumping out at me. Right now I'm just wanting to send my request and get the valid response that I'm expecting and I'll be happy.
For S&Gs, here's my soap request body too (taken from SoapUI):
<soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/" xmlns:ins="http://hostname/vantive/ws/InstalledComponentService2">
<soapenv:Header/>
<soapenv:Body>
<ins:getICDetailsByAssetTag>
<assetTag>asserTag</assetTag>
</ins:getICDetailsByAssetTag>
</soapenv:Body>
</soapenv:Envelope>
EDIT
After looking at this I can confirm that my ObjectFactory class (that was created via the wsdl) does have create methods that are wrapped with the JAXBElement wrapper, have #XmlElementDecl(namespace = "http://hostname/vantive/ws/InstalledComponentService2", xxx), and also name tags that correspond to the class that is wrapped by the JAXBElement wrapper.
EDIT 2
Just for clarity's sake, here is my package-info class
#XmlSchema(namespace = "http://hostname/vantive/ws/InstalledComponentService2")
package vantiveGenericWebService.wsdl;