Java: JAXB generation

Generating classes from XML schema:

<plugin>
  <groupId>org.codehaus.mojo</groupId>
  <artifactId>jaxb2-maven-plugin</artifactId>
  <version>${jaxb2.version}</version>
  <executions>
    <execution>
    <phase>none</phase>
    <id>xjc</id>
    <goals>
      <goal>xjc</goal>
    </goals>
    </execution>
  </executions>
  <configuration>
    <!-- The package of your generated sources -->
    <packageName>me.sample.package.with.xml.schema</packageName>
    <sources>
      <source>src/main/schema</source>
    </sources>
    <outputDirectory>src/main/java</outputDirectory>
    <clearOutputDir>false</clearOutputDir>
  </configuration>
</plugin>

Additionally, we need a folder with our schema in the project:

jaxb

Now, we can generate the classes from the schema types…

jaxb_maven_plugin

These classes will land in the request package declared in the plugin.

Advertisements

AWS: S3 operations

To list all objects use:

private Try listAllFiles(String bucket, String key) {
        ListObjectsRequest listObjectsRequest = new ListObjectsRequest().withBucketName(bucket).withPrefix(key);
        return Try.of(() -> {
            final List objectListingList = new LinkedList();

            ObjectListing objectListing = amazonS3.listObjects(listObjectsRequest);
            objectListingList.add(objectListing);

            while (objectListing != null && objectListing.isTruncated()) {
                objectListing = amazonS3.listNextBatchOfObjects(objectListing);
                objectListingList.add(objectListing);
            }

            return objectListingList.stream()
                .flatMap(objectListing1 -> objectListing1.getObjectSummaries().stream())
                .collect(Collectors.toList());
        });
    }

Read file:

 

public Try getBytes(String bucket, String key) {
        log.info("Reading data from: " + bucket + "/" + key);
        try (InputStream s3is = amazonS3.getObject(bucket, key).getObjectContent();
             ByteArrayOutputStream outputStream = new ByteArrayOutputStream()) {
            byte[] buf = new byte[8192];
            int bytesRead;
            while ((bytesRead = s3is.read(buf)) != -1) {
                outputStream.write(buf, 0, bytesRead);
            }
            return Try.success(outputStream.toString());
        } catch (IOException e) {
            return Try.failure(e);
        }
    }

 

Gzip the content:

 

private Try saveGzip(String bucket, String forecastFaultsKey, byte[] ff) {
        return Try.of(() -> {
            ByteArrayOutputStream ffBaos = new ByteArrayOutputStream();
            try (OutputStream os = new GZIPOutputStream(ffBaos)) {
                os.write(ff);
            } catch (IOException e) {
                log.error(e.getMessage());
                throw new RuntimeException(e);
            }

            final byte[] ffCompressed = ffBaos.toByteArray();

            ObjectMetadata ob = new ObjectMetadata();
            ob.setContentLength(ffCompressed.length);
            ob.setContentType("application/octet-stream");

            amazonS3.putObject(bucket, forecastFaultsKey, new ByteArrayInputStream(ffCompressed), ob);

            return null;
        });
    }

private Try readGzip(String bucket, String key) {
        log.info("Reading data from: " + bucket + "/" + key);
        try (InputStream s3is = new GZIPInputStream(amazonS3.getObject(bucket, key).getObjectContent());
             ByteArrayOutputStream outputStream = new ByteArrayOutputStream()) {
            byte[] buf = new byte[8192];
            int bytesRead;
            while ((bytesRead = s3is.read(buf)) != -1) {
                outputStream.write(buf, 0, bytesRead);
            }
            return Try.success(outputStream.toString());
        } catch (IOException e) {
            return Try.failure(e);
        }
    }

AWS: API-Gateway – Lambda proxy integration

Sometimes it might be more convinient to use ‘Lambda proxy integration’ to handle all requests.

The code below requires:

It means that we pass the whole request to lambda (with no posibility to enhance it) and we let lambda build the full response.

Let’s have a look how such handling might look like in Java code:

import static io.vavr.API.$;
import static io.vavr.API.Case;

import com.amazonaws.services.lambda.runtime.Context;
import com.amazonaws.services.lambda.runtime.events.APIGatewayProxyRequestEvent;
import com.amazonaws.services.lambda.runtime.events.APIGatewayProxyResponseEvent;
import com.fasterxml.jackson.core.JsonProcessingException;
import io.vavr.API;
import io.vavr.collection.Seq;
import io.vavr.control.Try;
import org.apache.log4j.LogManager;
import org.apache.log4j.Logger;

import java.util.HashMap;
import java.util.Map;

@SuppressWarnings("unused")
public class Lambda {

    private static final Logger log = LogManager.getLogger(Lambda.class);

    @SuppressWarnings("unused")
    public APIGatewayProxyResponseEvent dispatchRequests(APIGatewayProxyRequestEvent requestEvent, Context context) throws JsonProcessingException {
        final APIGatewayProxyResponseEvent responseEvent = new APIGatewayProxyResponseEvent();
        responseEvent.setHeaders(defaultHeaders());

        final Validation validationResult = validateRequest(requestEvent, API_VERSION);
        if (validationResult.isInvalid()) {
            return setStatusCodeAndBody(responseEvent, 400, validationResult.getError().toJavaList());
        }

        final ApiRequestValidator.ApiRequest apiRequest = validationResult.get();
        final Try authorizationResult = authorize(apiRequest.getAuthorization());
        if (authorizationResult.isFailure()) {
            return setStatusCodeAndBody(responseEvent, 401, authorizationResult.getCause());
        }

        // dispatch request
        API.Match(apiRequest.getPath()).of(
            Case($("/" + API_VERSION + "/resource"),              resourceHandler()),
            Case($("/" + API_VERSION + "/resource/sub-resource"), subResourceHandler()),
            Case($(), o -> {
                throw new UnsupportedEndpointException(o);
            })
        )
            .onSuccess(endpointResult -> setStatusCodeAndBody(responseEvent, 200, endpointResult))
            .onFailure(throwable -> setStatusCodeAndBody(responseEvent, 500, throwable.getLocalizedMessage()));

        return responseEvent;
    }

    private Map defaultHeaders() {
        Map result = new HashMap();
        result.put("Content-Type", "application/json");
        result.put("Access-Control-Allow-Origin", "*");
        return result;
    }

    private APIGatewayProxyResponseEvent setStatusCodeAndBody(APIGatewayProxyResponseEvent responseEvent, int statusCode, Object body) {
        responseEvent.setStatusCode(statusCode);
        setBody(responseEvent, body);
        return responseEvent;
    }

    private Validation validateRequest(APIGatewayProxyRequestEvent event, String apiPrefix) {
        return new ApiRequestValidator(apiPrefix).validateRequest(event.getHeaders(), event.getPath(), event.getHttpMethod());
    }

}

Code above introduces a few lines where:

  • status codes are set i.e. 200, 401, 500
  • ‘Content-Type’ or other flags are set in response’s header

Therefore, lambda takes full responsibility for handling the original requests that arrive at API-Gateway, than map the url path to specific controller to handle the logic, and at the end build a valid response.

In fact, there are not many things that API-Gateway can do….its role is simplified just to passing the requests and responses…

It can be easily noticed by looking at the API-Gataway’s proxy integration panel:

aws-api-gateway-lambda-proxy-integration-panel

The ‘Integration’ parts are disabled or reduced with its functionality, while these parts are crucial when it comes to handling the requests and responses. Since we are using ‘lambda proxy integration’ we delegate the responsibily of handling the requests and response directly to lambda.

Below is the view of ‘Integration Request’ panel:

aws-api-gateway-lambda-proxy-integration-request

To set up the ‘lambda integration proxy’ read AWS documentation:

 

AWS: API-Gateway: dispatching requests with one Lambda

Sometimes it is more convinient to use one lambda that handles many endpoints from API-Gateway.

We can achive this by:

  • setting the mapping in GET method in

aws-api-gateway-handle-many-endpoints-with-one-lambda

  • next, we need to handle the incomming JSON object that is defined above and dispatch the request on the ‘path’ variable
public class DispatchRequest {

    private String path;
    private String token;

    public DispatchRequest(String path, String token) {
        this.path = path;
        this.token = token;
    }

}
  • and a piece of code that does the dispatch:
import com.amazonaws.services.lambda.runtime.Context;
import com.amazonaws.services.lambda.runtime.RequestStreamHandler;
import com.fasterxml.jackson.databind.ObjectMapper;
import io.vavr.API;
import io.vavr.control.Try;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;

import java.io.InputStream;
import java.io.OutputStream;

@SuppressWarnings("unused")
public class Lambda implements RequestStreamHandler {

    private static final Logger log = LogManager.getLogger(Lambda.class);
    private static final String apiVersion = "api-v1";

    @Override
    public void handleRequest(InputStream is, OutputStream os, Context context) {
        log.info("Triggered lambda");

        final ObjectMapper om = new ObjectMapper();
        final DispatchRequest apiRequest = om.readValue(is, DispatchRequest.class);

        final String path = apiRequest.getPath();
        final String token = apiRequest.getToken();

        API.Match(path).of(
            //@formatter:off
            API.Case(API.$("/" + apiVersion + "/some-resource"),               () -> runSomeCode() ),
            API.Case(API.$("/" + apiVersion + "/other-resource/sub-resource"), () -> authorize(token).andThenTry(() -> runOtherCode())),
            API.Case(API.$(), () -> Try.failure(new UnsupportedEndpointException(path)))
            //@formatter:on
        );

        log.info("Lambda finished.");
    }

}