All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.spotify.apollo.entity.CodecEntityMiddleware Maven / Gradle / Ivy

/*
 * -\-\-
 * Spotify Apollo Entity Middleware
 * --
 * Copyright (C) 2013 - 2016 Spotify AB
 * --
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * 
 *      http://www.apache.org/licenses/LICENSE-2.0
 * 
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 * -/-/-
 */
package com.spotify.apollo.entity;

import com.spotify.apollo.RequestContext;
import com.spotify.apollo.Response;
import com.spotify.apollo.Status;
import com.spotify.apollo.route.AsyncHandler;
import com.spotify.apollo.route.Middleware;
import com.spotify.apollo.route.SyncHandler;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.IOException;
import java.util.Objects;
import java.util.Optional;
import java.util.function.Function;

import javaslang.control.Either;
import okio.ByteString;

import static java.util.concurrent.CompletableFuture.completedFuture;

/**
 * A factory for creating middlewares that can be used to create routes that deal directly
 * with an api entity.
 *
 * A {@link EntityCodec} is used to define how to go between a {@link ByteString} and the
 * entity type used in your route handlers.
 */
class CodecEntityMiddleware implements EntityMiddleware {

  private static final Logger LOG = LoggerFactory.getLogger(CodecEntityMiddleware.class);

  private static final String CONTENT_TYPE = "Content-Type";

  private final EntityCodec codec;
  private final String contentType;

  CodecEntityMiddleware(EntityCodec codec) {
    this(codec, codec.defaultContentType());
  }

  CodecEntityMiddleware(EntityCodec codec, String contentType) {
    this.codec = Objects.requireNonNull(codec);
    this.contentType = Objects.requireNonNull(contentType);
  }

  @Override
  public  Middleware, SyncHandler>>
  serializerDirect(Class responseEntityClass) {
    return inner -> inner
        .map(Response::forPayload)
        .map(serialize(responseEntityClass));
  }

  @Override
  public  Middleware>, SyncHandler>>
  serializerResponse(Class responseEntityClass) {
    return inner -> inner
        .map(serialize(responseEntityClass));
  }

  @Override
  public  Middleware, AsyncHandler>>
  asyncSerializerDirect(Class responseEntityClass) {
    return inner -> inner
        .map(Response::forPayload)
        .map(serialize(responseEntityClass));
  }

  @Override
  public  Middleware>, AsyncHandler>>
  asyncSerializerResponse(Class responseEntityClass) {
    return inner -> inner
        .map(serialize(responseEntityClass));
  }

  @Override
  public  Middleware, SyncHandler>>
  direct(Class requestEntityClass) {
    return direct(requestEntityClass, requestEntityClass);
  }

  @Override
  public  Middleware, SyncHandler>>
  direct(Class requestEntityClass, Class responseEntityClass) {
    return inner -> withEntity(inner.asResponseHandler(), requestEntityClass)
        .map(serialize(responseEntityClass));
  }

  @Override
  public  Middleware, SyncHandler>>
  response(Class requestEntityClass) {
    return response(requestEntityClass, requestEntityClass);
  }

  @Override
  public  Middleware, SyncHandler>>
  response(Class requestEntityClass, Class responseEntityClass) {
    return inner -> withEntity(inner, requestEntityClass)
        .map(serialize(responseEntityClass));
  }

  @Override
  public  Middleware, AsyncHandler>>
  asyncDirect(Class requestEntityClass) {
    return asyncDirect(requestEntityClass, requestEntityClass);
  }

  @Override
  public  Middleware, AsyncHandler>>
  asyncDirect(Class requestEntityClass, Class responseEntityClass) {
    return inner -> withEntityAsync(inner.asResponseHandler(), requestEntityClass)
        .map(serialize(responseEntityClass));
  }

  @Override
  public  Middleware, AsyncHandler>>
  asyncResponse(Class requestEntityClass) {
    return asyncResponse(requestEntityClass, requestEntityClass);
  }

  @Override
  public  Middleware, AsyncHandler>>
  asyncResponse(Class requestEntityClass, Class responseEntityClass) {
    return inner -> withEntityAsync(inner, requestEntityClass)
        .map(serialize(responseEntityClass));
  }

  private  SyncHandler> withEntity(
      EntityResponseHandler inner,
      Class entityClass) {
    //noinspection unchecked
    return rc -> deserialize(rc, entityClass)
        .map(inner.apply(rc))
        .getOrElseGet(left -> (Response) left);
  }

  private  AsyncHandler> withEntityAsync(
      EntityAsyncResponseHandler inner,
      Class entityClass) {
    //noinspection unchecked
    return rc -> deserialize(rc, entityClass)
        .map(inner.apply(rc))
        .getOrElseGet(left -> completedFuture((Response) left));
  }

  private  Either, E> deserialize(RequestContext rc, Class entityClass) {
    final Optional payloadOpt = rc.request().payload();
    if (!payloadOpt.isPresent()) {
      return Either.left(Response.forStatus(
          Status.BAD_REQUEST
              .withReasonPhrase("Missing payload")));
    }

    final E entity;
    try {
      entity = codec.read(payloadOpt.get(), entityClass);
    } catch (IOException e) {
      LOG.warn("error", e);
      return Either.left(Response.forStatus(
          Status.BAD_REQUEST
              .withReasonPhrase("Payload parsing failed: " + e.getMessage())));
    }

    return Either.right(entity);
  }

  private  Function, Response> serialize(Class entityClass) {
    return response -> {
      final Optional entityOpt = response.payload();

      if (!entityOpt.isPresent()) {
        //noinspection unchecked
        return (Response) response;
      }

      final ByteString bytes;
      try {
        bytes = codec.write(entityOpt.get(), entityClass);
      } catch (IOException e) {
        LOG.error("error", e);
        return Response.forStatus(
            Status.INTERNAL_SERVER_ERROR
                .withReasonPhrase("Payload serialization failed: " + e.getMessage()));
      }

      return response.withPayload(bytes)
          .withHeader(CONTENT_TYPE, contentType);
    };
  }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy