Amazon AppConfig from Spring Boot - amazon-web-services

How can I access configurations from aws appconfig, in my spring boot application?
Since appconfig is a new service, is there any java sdk that we can use, cos i dont see anything for appconfig yet in https://github.com/aws/aws-sdk-java/tree/master/src/samples

Here's how I've integrated AWS AppConfig into my Spring Boot project.
First, let’s make sure we have this dependency in our pom.xml:
<dependency>
<groupId>com.amazonaws</groupId>
<artifactId>aws-java-sdk-appconfig</artifactId>
<version>1.12.134</version>
</dependency>
Next, let’s create a simple configuration class of our own AWS AppConfig Client:
#Configuration
public class AwsAppConfiguration {
private static final Logger LOGGER = LoggerFactory.getLogger(AwsAppConfiguration.class);
private final AmazonAppConfig appConfig;
private final GetConfigurationRequest request;
public AwsAppConfiguration() {
appConfig = AmazonAppConfigClient.builder().build();
request = new GetConfigurationRequest();
request.setClientId("clientId");
request.setApplication("FeatureProperties");
request.setConfiguration("JsonProperties");
request.setEnvironment("dev");
}
public JSONObject getConfiguration() throws UnsupportedEncodingException {
GetConfigurationResult result = appConfig.getConfiguration(request);
String message = String.format("contentType: %s", result.getContentType());
LOGGER.info(message);
if (!Objects.equals("application/json", result.getContentType())) {
throw new IllegalStateException("config is expected to be JSON");
}
String content = new String(result.getContent().array(), "ASCII");
return new JSONObject(content).getJSONObject("feature");
}
}
Lastly, let’s create a scheduled task that polls the configuration from AWS AppConfig:
#Configuration
#EnableScheduling
public class AwsAppConfigScheduledTask {
private static final Logger LOGGER = LoggerFactory.getLogger(AwsAppConfigScheduledTask.class);
#Autowired
private FeatureProperties featureProperties;
#Autowired
private AwsAppConfiguration appConfiguration;
#Scheduled(fixedRate = 5000)
public void pollConfiguration() throws UnsupportedEncodingException {
LOGGER.info("polls configuration from aws app config");
JSONObject externalizedConfig = appConfiguration.getConfiguration();
featureProperties.setEnabled(externalizedConfig.getBoolean("enabled"));
featureProperties.setLimit(externalizedConfig.getInt("limit"));
}
}
I came across this question, as I was also trying to figure out how to best integrate AWS AppConfig into Spring Boot.
Here's an article I created. You can visit it here: https://levelup.gitconnected.com/create-features-toggles-using-aws-appconfig-in-spring-boot-7454b122bf91
Also, the source code is available on github: https://github.com/emyasa/medium-articles/tree/master/aws-spring-boot/app-config

First I added dependencies
<dependency>
<groupId>software.amazon.awssdk</groupId>
<artifactId>appconfig</artifactId>
<version>2.18.41</version>
</dependency>
<!-- https://mvnrepository.com/artifact/software.amazon.awssdk/appconfigdata -->
<dependency>
<groupId>software.amazon.awssdk</groupId>
<artifactId>appconfigdata</artifactId>
<version>2.19.4</version>
</dependency>
Then build the client
client = AppConfigDataClient.builder()
.credentialsProvider(() -> AwsBasicCredentials.create("<your id>", "your secret key"))
.region(Region.<your region>)
.build();
Use the client to start the configuration session
StartConfigurationSessionRequest startConfigurationSessionRequest = StartConfigurationSessionRequest.builder()
.applicationIdentifier("<your application id>")
.environmentIdentifier("your environment id")
.configurationProfileIdentifier("your config id")
.build();
Get the session token in the beginning and use it for the initial call.
String sessionToken = client.startConfigurationSession(startConfigurationSessionRequest).initialConfigurationToken();
GetLatestConfigurationRequest latestConfigurationRequest = GetLatestConfigurationRequest.builder()
.configurationToken(sessionToken)
.build();
GetLatestConfigurationResponse latestConfigurationResponse = client.getLatestConfiguration(latestConfigurationRequest);
String response = latestConfigurationResponse.configuration().asUtf8String();
You can use the next token available in the response to make the next call. The token can be cached as required.

2023, use aws-java-sdk-appconfigdata
<dependency>
<groupId>com.amazonaws</groupId>
<artifactId>aws-java-sdk-appconfigdata</artifactId>
<version>1.12.394</version>
</dependency>
example:
#Slf4j
#Configuration
#EnableScheduling
public class AWSAppConfig {
private String token;
private final AWSAppConfigData client;
public AWSAppConfig() {
log.info("init app config");
var client = AWSAppConfigDataClient.builder().build();
var request = new StartConfigurationSessionRequest();
request.setEnvironmentIdentifier("prod");
request.setApplicationIdentifier("my-app");
request.setConfigurationProfileIdentifier("my-config");
request.setRequiredMinimumPollIntervalInSeconds(15);
var result = client.startConfigurationSession(request);
this.client = client;
this.token = result.getInitialConfigurationToken();
}
#Scheduled(fixedRate = 20000)
public void pollConfiguration() {
var request = new GetLatestConfigurationRequest();
request.setConfigurationToken(token);
var result = client.getLatestConfiguration(request);
this.token = result.getNextPollConfigurationToken();
var configuration = StandardCharsets.UTF_8.decode(result.getConfiguration()).toString();
log.info("content type: {}", result.getContentType());
log.info("configuration: {}", configuration);
}
}

Related

Why won't unit tests connect to a websocket

UPDATE: I've uploaded a repo - https://github.com/mrpmorris/CannotIntegrationTestWebApp/blob/master/TestProject1/UnitTest1.cs
I have a web server that serves both HTTPS and WebSocket requests. When I run the app I am able to connect and make requests from postman for both HTTPS://localhost:8080 and WSS://localhost:8080/game-server
using Gambit.ApplicationLayer;
using Gambit.GameServer.Configuration;
using Gambit.GameServer.UseCases;
namespace Gambit.GameServer;
public class Program
{
public static async Task Main(string[] args)
{
WebApplication app = BuildApp(args);
await RunAppAsync(app);
}
public static WebApplication BuildApp(string[] args, Action<WebApplicationBuilder>? configure = null)
{
WebApplicationBuilder builder = WebApplication.CreateBuilder(args);
IServiceCollection services = builder.Services;
IConfiguration configuration = builder.Configuration;
IWebHostEnvironment environment = builder.Environment;
services.AddControllers();
services.AddLogging(opts =>
{
opts.ClearProviders();
opts.AddConfiguration(configuration.GetSection("Logging"));
opts.AddDebug();
opts.AddEventSourceLogger();
#if DEBUG
if (environment.IsDevelopment())
opts.AddConsole();
#endif
});
services.Configure<GameServerOptions>(configuration.GetSection("GameServer"));
services.AddApplicationServices(configuration);
configure?.Invoke(builder);
WebApplication app = builder.Build();
return app;
}
public static async Task RunAppAsync(WebApplication app)
{
app.MapGet("/", () => "Gambit.Server.API is running");
app.AddUserUseCases();
app.AddGameUseCases();
app.MapControllers();
app.UseWebSockets();
await app.RunAsync();
}
}
When I run my unit tests I use the same code to create and run the server (once per test run) my tests are able to make HTTPS requests but not connect via a WebSocket. When I try, I get a 404 error. I experience the same in PostMan.
static IntegrationTestsServer()
{
ConfigureMocks();
Environment.SetEnvironmentVariable("ASPNETCORE_ENVIRONMENT", "IntegrationTesting");
var app = Program.BuildApp(Array.Empty<string>(), builder =>
{
builder.WebHost.UseSetting("urls", "https://localhost:8080");
});
Configuration = app.Services.GetRequiredService<IConfiguration>();
GameServerOptions = app.Services.GetRequiredService<IOptions<GameServerOptions>>();
var dbContextOptions = app.Services.GetRequiredService<DbContextOptions<ApplicationDbContext>>();
using var dbContext = new ApplicationDbContext(dbContextOptions);
dbContext.Database.EnsureDeleted();
dbContext.Database.EnsureCreated();
HttpClient = new HttpClient { BaseAddress = new Uri("https://localhost:8080") };
_ = Program.RunAppAsync(app);
}
I can even perform a successful HttpClient.GetAsync("https://localhost:8080") immediately before the ClientWebSocket fails
System.Net.WebSockets.WebSocketException : The server returned status code '404' when status code '101' was expected.
Does anyone have any ideas why this might be?
Set ApplicationName in the WebApplicationOptions sent to WebApplication.CreateBuilder
WebApplication.CreateBuilder
(
new WebApplicationOptions
{
ApplicationName = typeof(Gambit.GameServer.Program).Assembly.GetName().Name // <==
}
);
Now it will be able to find your manifest file when running from a test.
See the following blog post for more of the back story on how I figured it out.
https://thefreezeteam.com/posts/StevenTCramer/2022/08/25/runwebserverintest

Automating user creation for AWS transfer family or secrets manager

I created a sftp server to upload files to S3, i have setup ssh authentication and username/password for the server.
Now i want to automate the process of creating users for the server using a lambda function, either by automating the secrets manager part or the aws transfer family user, is there a way to do this or what i am looking for is not yet possible
Java example using the AWS transfer family SDK:
public byte[] createUser(String sftpBaseFolderPath, String username) throws NoSuchAlgorithmException, IOException, JSchException {
CreateUserRequest createUserRequest = new CreateUserRequest();
createUserRequest.setServerId(serverId);
createUserRequest.setHomeDirectoryType("LOGICAL");
createUserRequest.setRole(userRoleArn);
createUserRequest.setUserName(username);
List<HomeDirectoryMapEntry> mapEntries = new ArrayList<>();
HomeDirectoryMapEntry homeDirectoryMapEntry = new HomeDirectoryMapEntry();
homeDirectoryMapEntry.setEntry("/");
homeDirectoryMapEntry.setTarget(sftpBaseFolderPath);
mapEntries.add(homeDirectoryMapEntry);
createUserRequest.setHomeDirectoryMappings(mapEntries);
KeyPair keyPair = KeyPair.genKeyPair(new JSch(), KeyPair.RSA, 2048);
ByteArrayOutputStream privateKey = new ByteArrayOutputStream();
OutputStream publicKey = new ByteArrayOutputStream();
keyPair.writePrivateKey(privateKey);
keyPair.writePublicKey(publicKey, "Key description/comment");
privateKey.close();
publicKey.close();
createUserRequest.setSshPublicKeyBody(publicKey.toString());
transferClient.createUser(createUserRequest);
return privateKey.toByteArray();
}
Transfer client initialization will be something like this:
public AWSStaticCredentialsProvider awsCredentialsProvider() {
return new AWSStaticCredentialsProvider(new BasicAWSCredentials(accessKey, secretKey));
}
public void initTransferClient() {
this.transferClient = AWSTransferClientBuilder
.standard()
.withCredentials(awsCredentialsProvider())
.withRegion(Regions.fromName(region))
.build();
}
SDK version
<dependency>
<groupId>com.amazonaws</groupId>
<artifactId>aws-java-sdk-transfer</artifactId>
<version>1.12.173</version>
</dependency>

Start embedded Jetty using WebApplicationInitializer

I am creating Restful (Jax-RS) services to be deployed to Fuse 6.2.1.
(using Apache CFX, and deploying with OSGi bundles to Karaf)
The server supports only up to Spring 3.2.12.RELEASE.
I am attempting to do everything with next to zero XML configuration.
So far so good, everything is working and I can deploy and run my services.
However, I'd like to be able to test my services locally without having to deploy them. So I'd like to be able to boostrap a webserver and register my servlet, but can't quite figure our how.
I'm configuring the servlet with this (using Spring's WebApplicationInitializer rather than web.xml):
import javax.servlet.ServletContext;
import javax.servlet.ServletException;
import javax.servlet.ServletRegistration;
import org.apache.cxf.transport.servlet.CXFServlet;
import org.springframework.web.WebApplicationInitializer;
import org.springframework.web.context.ContextLoaderListener;
import org.springframework.web.context.WebApplicationContext;
import org.springframework.web.context.support.AnnotationConfigWebApplicationContext;
public class CxfServletInitializer implements WebApplicationInitializer {
#Override
public void onStartup(ServletContext servletContext) throws ServletException {
servletContext.addListener(new ContextLoaderListener(createWebAppContext()));
addApacheCxfServlet(servletContext);
}
private void addApacheCxfServlet(ServletContext servletContext) {
CXFServlet cxfServlet = new CXFServlet();
ServletRegistration.Dynamic appServlet = servletContext.addServlet("CXFServlet", cxfServlet);
appServlet.setLoadOnStartup(1);
Set<String> mappingConflicts = appServlet.addMapping("/*");
}
private WebApplicationContext createWebAppContext() {
AnnotationConfigWebApplicationContext appContext = new AnnotationConfigWebApplicationContext();
appContext.register(CxfServletConfig.class);
return appContext;
}
}
And my main Spring config looks like this:
import javax.ws.rs.core.Application;
import javax.ws.rs.ext.RuntimeDelegate;
import org.apache.cxf.bus.spring.SpringBus;
import org.apache.cxf.endpoint.Server;
import org.apache.cxf.jaxrs.JAXRSServerFactoryBean;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.DependsOn;
import com.fasterxml.jackson.jaxrs.json.JacksonJsonProvider;
#Configuration
public class CxfServletConfig {
private static final org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(CxfServletConfig.class);
#Bean(destroyMethod = "shutdown")
public SpringBus cxf() {
return new SpringBus();
}
#Bean
#DependsOn("cxf")
public Server jaxRsServer(ApplicationContext appContext) {
JAXRSServerFactoryBean endpoint = RuntimeDelegate.getInstance().
createEndpoint(jaxRsApiApplication(), JAXRSServerFactoryBean.class);
endpoint.setServiceBeans(Arrays.<Object> asList(testSvc()));
endpoint.setAddress(endpoint.getAddress());
endpoint.setProvider(jsonProvider());
return endpoint.create();
}
#Bean
public Application jaxRsApiApplication() {
return new Application();
}
#Bean
public JacksonJsonProvider jsonProvider() {
return new JacksonJsonProvider();
}
#Bean(name = "testSvc")
public TestService testSvc() {
return new TestService();
}
So just to be clear, the above code is my current, working, deployable configuration. So now I'd like to create a test config that utilizes the same but which also starts Jetty and registers my servlet, and can't quite figure out how. Any help?
Thanks!
EDIT: Turns out I did not need the WebApplicationInitializer at all to get this to work. I ended up creating a Test config for Spring that defines a Jetty server as a bean. Seems to work:
#Configuration
public class TestingSpringConfig {
#Bean (name="jettyServer", destroyMethod = "stop")
public Server jettyServer() throws Exception {
Server server = new Server(0); //start jetty on a random, free port
// Register and map the dispatcher servlet
final ServletHolder servletHolder = new ServletHolder( new CXFServlet() );
final ServletContextHandler context = new ServletContextHandler();
context.setContextPath( "/" );
//fuse uses cxf as base url path for cxf services, so doing so as well here so urls are consistent
context.addServlet( servletHolder, "/mybaseurl/*" );
context.addEventListener( new ContextLoaderListener() );
context.setInitParameter( "contextClass", AnnotationConfigWebApplicationContext.class.getName() );
//this will load the spring config for the CFX servlet
context.setInitParameter( "contextConfigLocation", CxfServletConfig.class.getName() );
server.setHandler( context );
server.start();
//server.join(); if running from a main class instead of bean
return server;
}
#Bean(name = "jettyPort")
#DependsOn("jettyServer")
public Integer jettyPort() throws Exception {
Integer port = jettyServer().getConnectors()[0].getLocalPort();
log.info("Jetty started on port: " + port);
return port;
}
}

Unable to get collection response from cxf rest web service

I am trying to get java.util.List as response from cxf rest web service.
I have tried with WebClient class's method postObjectGetCollection method but no luck.
I am getting - org.apache.cxf.jaxrs.client.ClientWebApplicationException: .No message body reader has been found for class : interface java.util.Collection, ContentType : application/json.
Below are my client code-
String mediaType = "application/json";
if (url != null) {
List<DataTypeDTO> resultdtos = new ArrayList<DataTypeDTO>();
WebClient client = WebClient.create(url);
client = client.accept(mediaType).type(mediaType).path(uri);
resultdtos = (List<DataTypeDTO>)client.getCollection(DataTypeDTO.class);
System.out.println(resultdtos);
}
Please help me out if i am missing any configuration or other things.
You need to provide the provider list while creating the webClient object in your rest client.
You can use the below code to resolve your issue:
final String url = "http://localhost:10227/someService";
final String uri = "/manageXyz/fetchAllDataTypes";
final String mediaType = "application/json";
Object response = null;
List<Object> providers = new ArrayList<Object>();
providers.add( new JacksonJaxbJsonProvider() );
WebClient client = WebClient.create(url, providers);
client = client.accept(mediaType).type(mediaType).path(uri);
response = (List<Object>)client.post(oemUser, List.class);
If you are using maven, you also need to provide below required jars to resolve maven dependency in your project:
<dependency>
<groupId>org.codehaus.jackson</groupId>
<artifactId>jackson-jaxrs</artifactId>
<version>1.5.4</version>
</dependency>
<dependency>
<groupId>org.codehaus.jackson</groupId>
<artifactId>jackson-core-asl</artifactId>
<version>1.9.13</version>
</dependency>
<dependency>
<groupId>org.codehaus.jackson</groupId>
<artifactId>jackson-xc</artifactId>
<version>1.9.13</version>
</dependency>

A message body writer for Java type, class myPackage.B, and MIME media type, application/octet-stream, was not found

I am new at RESTful webservices and was trying to update my #OneToMany relationship from a standalone client application, but I am not able to do that. I am using the Jersey implementation of JAX-RS that ships with Glassfish 3.1.1.
I have a class A that has a #OneToMany relationship with class B.
MyRestClient is my standalone client that is calling my RESTful webservice which has been deployed on Glassfish 3.1.1.
MyRestClient.java
public class MyRestClient {
public static void main(String[] args) {
Client client = Client.create();
WebResource resource = client.resource("http://localhost:8080/myapp/rest/a/update/123");
B b1 = new B("debris");
ClientResponse response = resource.put(ClientResponse.class, b1);
System.out.println(response.getEntity(A.class).getTitle() + " has " + response.getEntity(A.class).getBList().size() + " Bs.");
}
}
AResource is an EJB session bean which I am using as RESTful webservice.
AResource.java
#Stateless
#Path("/a")
public class AResource {
#EJB
private AManager aManager;
#PUT
#Consumes(MediaType.APPLICATION_XML)
#Produces(MediaType.APPLICATION_XML)
#Path("/update/{id}")
public Response updateA(B b, #PathParam("id") int id) {
A a = aManager.findAById(id);
a.addB(b);
return Response.status(Status.OK).entity(a).build();
}
}
When I run the client I get the following error message:
com.sun.jersey.api.client.ClientHandlerException: A message body writer for Java type, class myPackage.B, and MIME media type, application/octet-stream, was not found.
Following are the domain objects in my standalone client application which is making a call to the AResource EJB session bean which I am using as the RESTful webservice.
A.java
#XmlRootElement
public class A implements Serializable{
private List<B> bList = new ArrayList<B>();
public List<B> getBList() {
return bList;
}
//remaining code
}
B.java
public class B implements Serializable {
private String text;
private A a;
#XmlTransient
public A getA() {
return a;
}
public void afterUnmarshal(Unmarshaller u, Object parent) {
this.a = (A) parent;
}
//remaining code
}
Could someone help me understand why this is happening and how I should solve this problem?
In your client code you are not specifying the content type of the data you are sending - so Jersey is not able to locate the right MessageBodyWritter to serialize the b1 object.
Modify the last line of your main method as follows:
ClientResponse response = resource.type(MediaType.APPLICATION_XML).put(ClientResponse.class, b1);
And add #XmlRootElement annotation to class B on both the server as well as the client sides.
Include this dependencies in your POM.xml and run Maven -> Update
<dependency>
<groupId>com.sun.jersey</groupId>
<artifactId>jersey-json</artifactId>
<version>1.18.1</version>
</dependency>
<dependency>
<groupId>com.owlike</groupId>
<artifactId>genson</artifactId>
<version>0.99</version>
</dependency>
You need to specify the #Provider that #Produces(MediaType.APPLICATION_XML) from B.class
An add the package of your MessageBodyWriter<B.class> to your /WEB_INF/web.xml as:
<init-param>
<param-name>com.sun.jersey.config.property.packages</param-name>
<param-value>
your.providers.package
</param-value>
</init-param>
You have to do two things to remove this error.
The #xmlElement mapping in the model
The client side:
response = resource.type(MediaType.APPLICATION_XML).put(ClientResponse.class, b1); //consume
or
response = resource.accept(MediaType.APPLICATION_XML).put(ClientResponse.class, b1); //produce
Adding reference to:
<dependency>
<groupId>com.sun.jersey</groupId>
<artifactId>jersey-json</artifactId>
<version>${jersey1.version}</version>
</dependency>
As long as adding clientConfig.getFeatures().put(JSONConfiguration.FEATURE_POJO_MAPPING, true); on client creation solved the issue for me:
ClientConfig clientConfig = new DefaultClientConfig();
clientConfig.getFeatures().put(JSONConfiguration.FEATURE_POJO_MAPPING, true);
Client client = Client.create(clientConfig);
This can also happen if you've recently upgraded Ant. I was using Ant 1.8.4 on a project, and upgraded Ant to 1.9.4, and started to get this error when building a fat jar using Ant.
The solution for me was to downgrade back to Ant 1.8.4 for the command line and Eclipse using the process detailed here
i was facing the same problem for a get method i was returning an "int" for the #get method Strangely when i change the return type to String the error was gone.Give it a try and if someone knows the logic behind it kindly share it
This solved my issue.
http://www.markhneedham.com/blog/2012/11/28/jersey-com-sun-jersey-api-client-clienthandlerexception-a-message-body-reader-for-java-class-and-mime-media-type-applicationjson-was-not-found/
Including following dependencies in your POM.xml and run Maven -> Update also fixed my issue.
<dependency>
<groupId>com.sun.jersey</groupId>
<artifactId>jersey-json</artifactId>
<version>1.19.1</version>
</dependency>
<dependency>
<groupId>com.owlike</groupId>
<artifactId>genson</artifactId>
<version>0.99</version>
</dependency>
This also happens if you're missing an empty public constructor for the Entity (could be for JSON, XML etc)..
Make sure that all these libs are in your class path:
compile(group: 'com.sun.jersey', name: 'jersey-core', version: '1.19.4')
compile(group: 'com.sun.jersey', name: 'jersey-server', version: '1.19.4')
compile(group: 'com.sun.jersey', name: 'jersey-servlet', version: '1.19.4')
compile(group: 'com.sun.jersey', name: 'jersey-json', version: '1.19.4')
compile(group: 'com.sun.jersey', name: 'jersey-client', version: '1.19.4')
compile(group: 'javax.ws.rs', name: 'jsr311-api', version: '1.1.1')
compile(group: 'org.codehaus.jackson', name: 'jackson-core-asl', version: '1.9.2')
compile(group: 'org.codehaus.jackson', name: 'jackson-mapper-asl', version: '1.9.2')
compile(group: 'org.codehaus.jackson', name: 'jackson-core-asl', version: '1.9.2')
compile(group: 'org.codehaus.jackson', name: 'jackson-jaxrs', version: '1.9.2')
compile(group: 'org.codehaus.jackson', name: 'jackson-xc', version: '1.9.2')
Add "Pojo Mapping" and "Jackson Provider" to the jersey client config:
ClientConfig clientConfig = new DefaultClientConfig();
clientConfig.getFeatures().put(JSONConfiguration.FEATURE_POJO_MAPPING, Boolean.TRUE);
clientConfig.getClasses().add(JacksonJsonProvider.class);
This solve to me!
ClientResponse response = null;
response = webResource
.type(MediaType.APPLICATION_JSON)
.accept(MediaType.APPLICATION_JSON)
.get(ClientResponse.class);
if (response.getStatus() == Response.Status.OK.getStatusCode()) {
MyClass myclass = response.getEntity(MyClass.class);
System.out.println(myclass);
}