package com.linkedin.parseq;

import com.linkedin.parseq.exec.Exec;
import com.linkedin.parseq.function.Success;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.StandardCopyOption;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.TimeUnit;
import javax.servlet.http.HttpServletResponse;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class GraphvizEngine {

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

  private final String _dotLocation;
  private final Path _cacheLocation;
  private final long _timeoutMs;
  private final HashManager _hashManager;
  private final Exec _exec;
  private final ConcurrentHashMap<String, Task<Exec.Result>> _inFlightBuildTasks;

  public GraphvizEngine(final String dotLocation, final Path cacheLocation, final int cacheSize, final long timeoutMs,
      final int numThreads, final long reaperDelayMs, final int processQueueSize) {
    _dotLocation = dotLocation;
    _cacheLocation = cacheLocation;
    _timeoutMs = timeoutMs;
    _hashManager = new HashManager(this::removeCached, cacheSize);
    _exec = new Exec(numThreads, reaperDelayMs, processQueueSize);
    _inFlightBuildTasks = new ConcurrentHashMap<>();
  }

  public void start() {
    _exec.start();
  }

  public void stop() {
    _exec.stop();
  }

  /**
   * Return task that has general HTTP status and body information based on the build task's result.
   */
  public Task<HttpResponse> build(final String hash, final InputStream body)
      throws IOException {
    if (hash == null) {
      // Missing hash
      String content = "Missing hash.";
      LOG.info(content);
      return Task.value(new HttpResponse(HttpServletResponse.SC_BAD_REQUEST, content));
    } else {
      // Have cache
      if (_hashManager.contains(hash)) {
        LOG.info("hash found in cache: " + hash);
        return Task.value(new HttpResponse(HttpServletResponse.SC_OK, ""));
      } else {
        if (body == null) {
          // Missing body
          String content = "Missing body.";
          LOG.info(content);
          return Task.value(new HttpResponse(HttpServletResponse.SC_BAD_REQUEST, content));
        } else if (_dotLocation == null) {
          // Missing dot
          String content = "Missing dot.";
          LOG.info(content);
          return Task.value(new HttpResponse(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, content));
        } else {
          // Build task
          final Task<Exec.Result> buildTask = getBuildTask(hash, body);
          return buildTask.transform("result", result -> {
            Integer status = null;
            String content = null;
            if (result.isFailed()) {
              // Task fail
              status = HttpServletResponse.SC_INTERNAL_SERVER_ERROR;
              content = result.getError().toString();
            } else {
              // Task success
              switch (result.get().getStatus()) {
                // Success
                case 0:
                  _hashManager.add(hash);
                  status = HttpServletResponse.SC_OK;
                  content = "";
                  break;
                // Timeout
                case 137:
                  status = HttpServletResponse.SC_INTERNAL_SERVER_ERROR;
                  content = "graphviz process was killed because it did not finish within " + _timeoutMs + "ms";
                  break;
                // Unknown
                default:
                  status = HttpServletResponse.SC_INTERNAL_SERVER_ERROR;
                  content = writeGenericFailureInfo(result.get());
                  break;
              }
            }
            // Clean up cache
            _inFlightBuildTasks.remove(hash, buildTask);
            return Success.of(new HttpResponse(status, content));
          });
        }
      }
    }
  }

  /**
   * Returns task that builds graph using graphviz. Returned task might be shared with other concurrent requests.
   */
  private Task<Exec.Result> getBuildTask(final String hash, final InputStream body) {
    Task<Exec.Result> existing = _inFlightBuildTasks.get(hash);
    if (existing != null) {
      LOG.info("using in flight shareable: " + hash);
      return existing.shareable();
    } else {
      Task<Exec.Result> newBuildTask = createNewBuildTask(hash, body);
      existing = _inFlightBuildTasks.putIfAbsent(hash, newBuildTask);
      if (existing != null) {
        LOG.info("using in flight shareable: " + hash);
        return existing.shareable();
      } else {
        return newBuildTask;
      }
    }
  }

  /**
   * Returns new task that builds graph using graphviz.
   */
  private Task<Exec.Result> createNewBuildTask(final String hash, final InputStream body) {

    LOG.info("building: " + hash);

    final Task<Void> createDotFile = Task.action("createDotFile",
        () -> Files.copy(body, pathToCacheFile(hash, "dot"), StandardCopyOption.REPLACE_EXISTING));

    // Task that runs a graphviz command.
    // We give process TIMEOUT_MS time to finish, after that
    // it will be forcefully killed.
    final Task<Exec.Result> graphviz = _exec
        .command("graphviz", _timeoutMs, TimeUnit.MILLISECONDS, _dotLocation, "-T" + Constants.OUTPUT_TYPE,
            "-Grankdir=LR", "-Gnewrank=true", "-Gbgcolor=transparent", pathToCacheFile(hash, "dot").toString(), "-o",
            pathToCacheFile(hash, Constants.OUTPUT_TYPE).toString());

    // Since Exec utility allows only certain number of processes
    // to run in parallel and rest is enqueued, we also specify
    // timeout on a task level equal to 2 * graphviz timeout.
    final Task<Exec.Result> graphvizWithTimeout = graphviz.withTimeout(_timeoutMs * 2, TimeUnit.MILLISECONDS);

    return createDotFile.andThen(graphvizWithTimeout);
  }

  private Path pathToCacheFile(String hash, String ext) {
    return _cacheLocation.resolve(hash + "." + ext);
  }

  private File cacheFile(String hash, String ext) {
    return pathToCacheFile(hash, ext).toFile();
  }

  private void removeCached(String hash) {
    cacheFile(hash, Constants.OUTPUT_TYPE).delete();
    cacheFile(hash, "dot").delete();
  }

  /**
   * Writes error info to a String.
   */
  private String writeGenericFailureInfo(final Exec.Result result)
      throws IOException {
    StringBuilder sb = new StringBuilder();
    sb.append("graphviz process returned: ").append(result.getStatus()).append("\n").append("stdout:\n");
    Files.lines(result.getStdout()).forEach(sb::append);
    sb.append("stderr:\n");
    Files.lines(result.getStderr()).forEach(sb::append);
    return sb.toString();
  }
}


package com.linkedin.parseq;

import java.util.Collection;
import java.util.concurrent.TimeUnit;
import java.util.function.Function;

import com.linkedin.parseq.function.Tuple11;
import com.linkedin.parseq.promise.PromiseException;
import com.linkedin.parseq.promise.PromiseListener;
import com.linkedin.parseq.promise.PromiseUnresolvedException;
import com.linkedin.parseq.trace.ShallowTrace;
import com.linkedin.parseq.trace.ShallowTraceBuilder;
import com.linkedin.parseq.trace.Trace;
import com.linkedin.parseq.trace.TraceBuilder;

public class Tuple11TaskDelegate<T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11> implements Tuple11Task<T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11> {

  private final Task<Tuple11<T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11>> _task;

  public Tuple11TaskDelegate(Task<Tuple11<T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11>> task) {
    _task = task;
  }

  /**
   * {@inheritDoc}
   */
  @Override
  public boolean cancel(Exception reason) {
    return _task.cancel(reason);
  }

  /**
   * {@inheritDoc}
   */
  @Override
  public Tuple11<T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11> get() throws PromiseException {
    return _task.get();
  }

  /**
   * {@inheritDoc}
   */
  @Override
  public Throwable getError() throws PromiseUnresolvedException {
    return _task.getError();
  }

  /**
   * {@inheritDoc}
   */
  @Override
  public Tuple11<T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11> getOrDefault(Tuple11<T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11> defaultValue) throws PromiseUnresolvedException {
    return _task.getOrDefault(defaultValue);
  }

  /**
   * {@inheritDoc}
   */
  @Override
  public String getName() {
    return _task.getName();
  }

  /**
   * {@inheritDoc}
   */
  @Override
  public int getPriority() {
    return _task.getPriority();
  }

  /**
   * {@inheritDoc}
   */
  @Override
  public boolean setPriority(int priority) {
    return _task.setPriority(priority);
  }

  /**
   * {@inheritDoc}
   */
  @Override
  public void await() throws InterruptedException {
    _task.await();
  }

  /**
   * {@inheritDoc}
   */
  @Override
  public void contextRun(Context context, Task<?> parent, Collection<Task<?>> predecessors) {
    _task.contextRun(context, parent, predecessors);
  }

  /**
   * {@inheritDoc}
   */
  @Override
  public boolean await(long time, TimeUnit unit) throws InterruptedException {
    return _task.await(time, unit);
  }

  /**
   * {@inheritDoc}
   */
  @Override
  public ShallowTrace getShallowTrace() {
    return _task.getShallowTrace();
  }

  /**
   * {@inheritDoc}
   */
  @Override
  public void addListener(PromiseListener<Tuple11<T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11>> listener) {
    _task.addListener(listener);
  }

  /**
   * {@inheritDoc}
   */
  @Override
  public Trace getTrace() {
    return _task.getTrace();
  }

  /**
   * {@inheritDoc}
   */
  @Override
  public boolean isDone() {
    return _task.isDone();
  }

  /**
   * {@inheritDoc}
   */
  @Override
  public boolean isFailed() {
    return _task.isFailed();
  }

  /**
   * {@inheritDoc}
   */
  @Override
  public Long getId() {
    return _task.getId();
  }

  @Override
  public void setTraceValueSerializer(Function<Tuple11<T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11>, String> serializer) {
    _task.setTraceValueSerializer(serializer);
  }

  @Override
  public ShallowTraceBuilder getShallowTraceBuilder() {
    return _task.getShallowTraceBuilder();
  }

  @Override
  public TraceBuilder getTraceBuilder() {
    return _task.getTraceBuilder();
  }

}


/*
 * Copyright 2016 LinkedIn, Inc
 *
 * 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.linkedin.restli.client;

import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import java.util.function.Function;
import java.util.stream.Collectors;

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

import com.linkedin.common.callback.Callback;
import com.linkedin.data.DataMap;
import com.linkedin.data.schema.PathSpec;
import com.linkedin.data.template.RecordTemplate;
import com.linkedin.parseq.batching.Batch;
import com.linkedin.parseq.batching.BatchImpl.BatchEntry;
import com.linkedin.parseq.function.Tuple3;
import com.linkedin.parseq.function.Tuples;
import com.linkedin.r2.RemoteInvocationException;
import com.linkedin.r2.message.RequestContext;
import com.linkedin.r2.message.rest.RestResponseBuilder;
import com.linkedin.restli.client.response.BatchKVResponse;
import com.linkedin.restli.common.BatchResponse;
import com.linkedin.restli.common.EntityResponse;
import com.linkedin.restli.common.ErrorResponse;
import com.linkedin.restli.common.HttpStatus;
import com.linkedin.restli.common.ProtocolVersion;
import com.linkedin.restli.common.ResourceMethod;
import com.linkedin.restli.common.ResourceSpec;
import com.linkedin.restli.common.RestConstants;
import com.linkedin.restli.internal.client.ResponseImpl;
import com.linkedin.restli.internal.client.response.BatchEntityResponse;
import com.linkedin.restli.internal.common.ProtocolVersionUtil;
import com.linkedin.restli.internal.common.ResponseUtils;

class GetRequestGroup implements RequestGroup {

  private static final Logger LOGGER = LoggerFactory.getLogger(GetRequestGroup.class);
  private static final RestLiResponseException NOT_FOUND_EXCEPTION =
      new RestLiResponseException(new RestResponseBuilder().setStatus(HttpStatus.S_404_NOT_FOUND.getCode()).build(),
          null, new ErrorResponse().setStatus(HttpStatus.S_404_NOT_FOUND.getCode()));

  private final String _baseUriTemplate; //taken from first request, used to differentiate between groups
  private final ResourceSpec _resourceSpec;  //taken from first request
  private final Map<String, String> _headers; //taken from first request, used to differentiate between groups
  private final RestliRequestOptions _requestOptions; //taken from first request, used to differentiate between groups
  private final Map<String, Object> _queryParams; //taken from first request, used to differentiate between groups
  private final Map<String, Object> _pathKeys; //taken from first request, used to differentiate between groups
  private final int _maxBatchSize;

  @SuppressWarnings("deprecation")
  public GetRequestGroup(Request<?> request, int maxBatchSize) {
    _baseUriTemplate = request.getBaseUriTemplate();
    _headers = request.getHeaders();
    _queryParams = getQueryParamsForBatchingKey(request);
    _resourceSpec = request.getResourceSpec();
    _requestOptions = request.getRequestOptions();
    _pathKeys = request.getPathKeys();
    _maxBatchSize = maxBatchSize;
  }

  private static Map<String, Object> getQueryParamsForBatchingKey(Request<?> request)
  {
    final Map<String, Object> params = new HashMap<>(request.getQueryParamsObjects());
    params.remove(RestConstants.QUERY_BATCH_IDS_PARAM);
    params.remove(RestConstants.FIELDS_PARAM);
    return params;
  }

  private static <K, RT extends RecordTemplate> Response<RT> unbatchResponse(BatchGetEntityRequest<K, RT> request,
      Response<BatchKVResponse<K, EntityResponse<RT>>> batchResponse, Object id) throws RemoteInvocationException {
    final BatchKVResponse<K, EntityResponse<RT>> batchEntity = batchResponse.getEntity();
    final ErrorResponse errorResponse = batchEntity.getErrors().get(id);
    if (errorResponse != null) {
      throw new RestLiResponseException(errorResponse);
    }

    final EntityResponse<RT> entityResponse = batchEntity.getResults().get(id);
    if (entityResponse != null) {
      final RT entityResult = entityResponse.getEntity();
      if (entityResult != null) {
        return new ResponseImpl<>(batchResponse, entityResult);
      }
    }

    LOGGER.debug("No result or error for base URI : {}, id: {}. Verify that the batchGet endpoint returns response keys that match batchGet request IDs.",
        request.getBaseUriTemplate(), id);

    throw NOT_FOUND_EXCEPTION;
  }

  private DataMap filterIdsInBatchResult(DataMap data, Set<String> ids) {
    DataMap dm = new DataMap(data.size());
    data.forEach((key, value) -> {
      switch(key) {
        case BatchResponse.ERRORS:
          dm.put(key, filterIds((DataMap)value, ids));
          break;
        case BatchResponse.RESULTS:
          dm.put(key, filterIds((DataMap)value, ids));
          break;
        case BatchResponse.STATUSES:
          dm.put(key, filterIds((DataMap)value, ids));
          break;
        default:
          dm.put(key, value);
          break;
      }
    });
    return dm;
  }

  private Object filterIds(DataMap data, Set<String> ids) {
    DataMap dm = new DataMap(data.size());
    data.forEach((key, value) -> {
      if (ids.contains(key)) {
        dm.put(key, value);
      }
    });
    return dm;
  }

  //Tuple3: (keys, fields, contains-batch-get)
  private static Tuple3<Set<Object>, Set<PathSpec>, Boolean> reduceRequests(final Tuple3<Set<Object>, Set<PathSpec>, Boolean> state,
      final Request<?> rq) {
    return reduceContainsBatch(reduceIds(reduceFields(state, rq), rq), rq);
  }

  //Tuple3: (keys, fields, contains-batch-get)
  private static Tuple3<Set<Object>, Set<PathSpec>, Boolean> reduceContainsBatch(Tuple3<Set<Object>, Set<PathSpec>, Boolean> state,
      Request<?> request) {
    if (request instanceof GetRequest) {
      return state;
    } else if (request instanceof BatchRequest) {
      return Tuples.tuple(state._1(), state._2(), true);
    } else {
      throw unsupportedGetRequestType(request);
    }
  }

  //Tuple3: (keys, fields, contains-batch-get)
  private static Tuple3<Set<Object>, Set<PathSpec>, Boolean> reduceIds(Tuple3<Set<Object>, Set<PathSpec>, Boolean> state,
      Request<?> request) {
    if (request instanceof GetRequest) {
      GetRequest<?> getRequest = (GetRequest<?>)request;
      state._1().add(getRequest.getObjectId());
      return state;
    } else if (request instanceof BatchRequest) {
      BatchRequest<?> batchRequest = (BatchRequest<?>)request;
      state._1().addAll(batchRequest.getObjectIds());
      return state;
    } else {
      throw unsupportedGetRequestType(request);
    }
  }

  //Tuple3: (keys, fields, contains-batch-get)
  private static Tuple3<Set<Object>, Set<PathSpec>, Boolean> reduceFields(Tuple3<Set<Object>, Set<PathSpec>, Boolean> state,
      Request<?> request) {
    if (request instanceof GetRequest || request instanceof BatchRequest) {
      final Set<PathSpec> requestFields = request.getFields();
      if (requestFields != null && !requestFields.isEmpty()) {
        if (state._2() != null) {
          state._2().addAll(requestFields);
        }
        return state;
      } else {
        return Tuples.tuple(state._1(), null, state._3());
      }
    } else {
      throw unsupportedGetRequestType(request);
    }
  }

  @SuppressWarnings({ "rawtypes", "unchecked" })
  private <K, RT extends RecordTemplate> void doExecuteBatchGet(final Client client,
    final Batch<RestRequestBatchKey, Response<Object>> batch, final Set<Object> ids, final Set<PathSpec> fields,
    Function<Request<?>, RequestContext> requestContextProvider) {
    final BatchGetEntityRequestBuilder<K, RT> builder = new BatchGetEntityRequestBuilder<>(_baseUriTemplate, _resourceSpec, _requestOptions);
    builder.setHeaders(_headers);
    _queryParams.forEach((key, value) -> builder.setParam(key, value));
    _pathKeys.forEach((key, value) -> builder.pathKey(key, value));

    builder.ids((Set<K>)ids);
    if (fields != null && !fields.isEmpty()) {
      builder.fields(fields.toArray(new PathSpec[fields.size()]));
    }

    final BatchGetEntityRequest<K, RT> batchGet = builder.build();

    client.sendRequest(batchGet, requestContextProvider.apply(batchGet), new Callback<Response<BatchKVResponse<K, EntityResponse<RT>>>>() {

      @Override
      public void onSuccess(Response<BatchKVResponse<K, EntityResponse<RT>>> responseToBatch) {
        final ProtocolVersion version = ProtocolVersionUtil.extractProtocolVersion(responseToBatch.getHeaders());
        batch.entries().stream()
        .forEach(entry -> {
          try {
            RestRequestBatchKey rrbk = entry.getKey();
            Request request = rrbk.getRequest();
            if (request instanceof GetRequest) {
              successGet((GetRequest) request, responseToBatch, batchGet, entry, version);
            } else if (request instanceof BatchGetKVRequest) {
              successBatchGetKV((BatchGetKVRequest) request, responseToBatch, entry, version);
            } else if (request instanceof BatchGetRequest) {
              successBatchGet((BatchGetRequest) request, responseToBatch, entry, version);
            } else if (request instanceof BatchGetEntityRequest) {
              successBatchGetEntity((BatchGetEntityRequest) request, responseToBatch, entry, version);
            } else {
              entry.getValue().getPromise().fail(unsupportedGetRequestType(request));
            }
          } catch (RemoteInvocationException e) {
            entry.getValue().getPromise().fail(e);
          }
        });
      }

      @SuppressWarnings({ "deprecation" })
      private void successBatchGetEntity(BatchGetEntityRequest request,
          Response<BatchKVResponse<K, EntityResponse<RT>>> responseToBatch,
          Entry<RestRequestBatchKey, BatchEntry<Response<Object>>> entry, final ProtocolVersion version) {
        Set<String> ids = (Set<String>) request.getObjectIds().stream()
            .map(o -> BatchResponse.keyToString(o, version))
            .collect(Collectors.toSet());
        DataMap dm = filterIdsInBatchResult(responseToBatch.getEntity().data(), ids);
        BatchKVResponse br = new BatchEntityResponse<>(dm, request.getResourceSpec().getKeyType(),
            request.getResourceSpec().getValueType(), request.getResourceSpec().getKeyParts(),
            request.getResourceSpec().getComplexKeyType(), version);
        Response rsp = new ResponseImpl(responseToBatch, br);
        entry.getValue().getPromise().done(rsp);
      }

      private void successBatchGet(BatchGetRequest request, Response<BatchKVResponse<K, EntityResponse<RT>>> responseToBatch,
          Entry<RestRequestBatchKey, BatchEntry<Response<Object>>> entry, final ProtocolVersion version) {
        Set<String> ids = (Set<String>) request.getObjectIds().stream()
            .map(o -> BatchResponse.keyToString(o, version))
            .collect(Collectors.toSet());
        DataMap dm = filterIdsInBatchResult(responseToBatch.getEntity().data(), ids);
        BatchResponse br = new BatchResponse<>(dm, request.getResponseDecoder().getEntityClass());
        Response rsp = new ResponseImpl(responseToBatch, br);
        entry.getValue().getPromise().done(rsp);
      }

      @SuppressWarnings({ "deprecation" })
      private void successBatchGetKV(BatchGetKVRequest request, Response<BatchKVResponse<K, EntityResponse<RT>>> responseToBatch,
          Entry<RestRequestBatchKey, BatchEntry<Response<Object>>> entry,
          final ProtocolVersion version) {
        Set<String> ids = (Set<String>) request.getObjectIds().stream()
            .map(o -> BatchResponse.keyToString(o, version))
            .collect(Collectors.toSet());
        DataMap dm = filterIdsInBatchResult(responseToBatch.getEntity().data(), ids);
        BatchKVResponse br = new BatchKVResponse(dm, request.getResourceSpec().getKeyType(),
            request.getResourceSpec().getValueType(), request.getResourceSpec().getKeyParts(),
            request.getResourceSpec().getComplexKeyType(), version);
        Response rsp = new ResponseImpl(responseToBatch, br);
        entry.getValue().getPromise().done(rsp);
      }

      @SuppressWarnings({ "deprecation" })
      private void successGet(GetRequest request,
          Response<BatchKVResponse<K, EntityResponse<RT>>> responseToBatch, final BatchGetEntityRequest<K, RT> batchGet,
          Entry<RestRequestBatchKey, BatchEntry<Response<Object>>> entry, final ProtocolVersion version)
              throws RemoteInvocationException {
        String idString = BatchResponse.keyToString(request.getObjectId(), version);
        Object id = ResponseUtils.convertKey(idString, request.getResourceSpec().getKeyType(),
            request.getResourceSpec().getKeyParts(), request.getResourceSpec().getComplexKeyType(), version);
        Response rsp = unbatchResponse(batchGet, responseToBatch, id);
        entry.getValue().getPromise().done(rsp);
      }

      @Override
      public void onError(Throwable e) {
        batch.failAll(e);
      }

    });
  }

  private static RuntimeException unsupportedGetRequestType(Request<?> request) {
    return new RuntimeException("ParSeqRestliClient could not handle this type of GET request: " + request.getClass().getName());
  }

  @SuppressWarnings({ "rawtypes", "unchecked" })
  private <K, RT extends RecordTemplate> void doExecuteGet(final Client client,
      final Batch<RestRequestBatchKey, Response<Object>> batch, final Set<Object> ids, final Set<PathSpec> fields,
      Function<Request<?>, RequestContext> requestContextProvider) {

    final GetRequestBuilder<K, RT> builder = (GetRequestBuilder<K, RT>) new GetRequestBuilder<>(_baseUriTemplate,
        _resourceSpec.getValueClass(), _resourceSpec, _requestOptions);
    builder.setHeaders(_headers);
    _queryParams.forEach((key, value) -> builder.setParam(key, value));
    _pathKeys.forEach((key, value) -> builder.pathKey(key, value));

    builder.id((K) ids.iterator().next());
    if (fields != null && !fields.isEmpty()) {
      builder.fields(fields.toArray(new PathSpec[fields.size()]));
    }

    final GetRequest<RT> get = builder.build();

    client.sendRequest(get, requestContextProvider.apply(get), new Callback<Response<RT>>() {

      @Override
      public void onError(Throwable e) {
        batch.failAll(e);
      }

      @Override
      public void onSuccess(Response<RT> responseToGet) {
        batch.entries().stream().forEach(entry -> {
          Request request = entry.getKey().getRequest();
          if (request instanceof GetRequest) {
            entry.getValue().getPromise().done(new ResponseImpl<>(responseToGet, responseToGet.getEntity()));
          } else {
            entry.getValue().getPromise().fail(unsupportedGetRequestType(request));
          }
        });
      }

    });
  }

  //Tuple3: (keys, fields, contains-batch-get)
  private Tuple3<Set<Object>, Set<PathSpec>, Boolean> reduceRequests(
      final Batch<RestRequestBatchKey, Response<Object>> batch) {
    return batch.entries().stream()
      .map(Entry::getKey)
      .map(RestRequestBatchKey::getRequest)
      .reduce(Tuples.tuple(new HashSet<>(), new HashSet<>(), false),
          GetRequestGroup::reduceRequests,
          GetRequestGroup::combine);
  }

  private static Tuple3<Set<Object>, Set<PathSpec>, Boolean> combine(Tuple3<Set<Object>, Set<PathSpec>, Boolean> a,
      Tuple3<Set<Object>, Set<PathSpec>, Boolean> b) {
    Set<Object> ids = a._1();
    ids.addAll(b._1());
    Set<PathSpec> paths = a._2();
    paths.addAll(b._2());
    return Tuples.tuple(ids, paths, a._3() || b._3());
  }

  @Override
  public <RT extends RecordTemplate> void executeBatch(final Client client, final Batch<RestRequestBatchKey, Response<Object>> batch,
      Function<Request<?>, RequestContext> requestContextProvider) {
    final Tuple3<Set<Object>, Set<PathSpec>, Boolean> reductionResults = reduceRequests(batch);
    final Set<Object> ids = reductionResults._1();
    final Set<PathSpec> fields = reductionResults._2();
    final boolean containsBatchGet = reductionResults._3();

    LOGGER.debug("executeBatch, ids: '{}', fields: {}", ids, fields);

    if (ids.size() == 1 && !containsBatchGet) {
      doExecuteGet(client, batch, ids, fields, requestContextProvider);
    } else {
      doExecuteBatchGet(client, batch, ids, fields, requestContextProvider);
    }
  }

  @Override
  public String getBaseUriTemplate() {
    return _baseUriTemplate;
  }

  public Map<String, String> getHeaders() {
    return _headers;
  }

  public Map<String, Object> getQueryParams() {
    return _queryParams;
  }

  public Map<String, Object> getPathKeys() {
    return _pathKeys;
  }

  public ResourceSpec getResourceSpec() {
    return _resourceSpec;
  }

  public RestliRequestOptions getRequestOptions() {
    return _requestOptions;
  }

  @Override
  public int hashCode() {
    final int prime = 31;
    int result = 1;
    result = prime * result + ((_baseUriTemplate == null) ? 0 : _baseUriTemplate.hashCode());
    result = prime * result + ((_headers == null) ? 0 : _headers.hashCode());
    result = prime * result + ((_queryParams == null) ? 0 : _queryParams.hashCode());
    result = prime * result + ((_pathKeys == null) ? 0 : _pathKeys.hashCode());
    result = prime * result + ((_requestOptions == null) ? 0 : _requestOptions.hashCode());
    return result;
  }

  @Override
  public boolean equals(Object obj) {
    if (this == obj)
      return true;
    if (obj == null)
      return false;
    if (getClass() != obj.getClass())
      return false;
    GetRequestGroup other = (GetRequestGroup) obj;
    if (_baseUriTemplate == null) {
      if (other._baseUriTemplate != null)
        return false;
    } else if (!_baseUriTemplate.equals(other._baseUriTemplate))
      return false;
    if (_headers == null) {
      if (other._headers != null)
        return false;
    } else if (!_headers.equals(other._headers))
      return false;
    if (_queryParams == null) {
      if (other._queryParams != null)
        return false;
    } else if (!_queryParams.equals(other._queryParams))
      return false;
    if (_pathKeys == null) {
      if (other._pathKeys != null)
        return false;
    } else if (!_pathKeys.equals(other._pathKeys))
      return false;
    if (_requestOptions == null) {
      if (other._requestOptions != null)
        return false;
    } else if (!_requestOptions.equals(other._requestOptions))
      return false;
    if (_resourceSpec == null){
      if (other._resourceSpec != null) {
        return false;
      }
    } else if (_resourceSpec.getKeyClass() != other._resourceSpec.getKeyClass()) {
      return false;
    }
    return true;
  }

  @Override
  public String toString() {
    return "GetRequestGroup [_baseUriTemplate=" + _baseUriTemplate + ", _queryParams=" + _queryParams + ", _pathKeys=" + _pathKeys
        + ", _requestOptions=" + _requestOptions + ", _headers=" + _headers + ", _maxBatchSize=" + _maxBatchSize + "]";
  }

  @Override
  public <K, V> String getBatchName(final Batch<K, V> batch) {
    return _baseUriTemplate + " " + (batch.batchSize() == 1 ? ResourceMethod.GET : (ResourceMethod.BATCH_GET +
        "(reqs: " + batch.keySize() + ", ids: " + batch.batchSize() + ")"));
  }

  @Override
  public int getMaxBatchSize() {
    return _maxBatchSize;
  }

  @Override
  public int keySize(RestRequestBatchKey key) {
    return key.ids().size();
  }

}


/*
 * Copyright 2016 LinkedIn Corp.
 *
 * 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.linkedin.parseq.zk.client;

import com.linkedin.parseq.Context;
import com.linkedin.parseq.Engine;
import com.linkedin.parseq.MultiException;
import com.linkedin.parseq.Task;
import com.linkedin.parseq.promise.Promise;
import com.linkedin.parseq.promise.PromiseListener;
import com.linkedin.parseq.promise.Promises;
import com.linkedin.parseq.promise.SettablePromise;
import java.io.IOException;
import java.util.ArrayList;
import java.util.EnumMap;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.concurrent.Executor;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean;
import org.apache.zookeeper.CreateMode;
import org.apache.zookeeper.KeeperException;
import org.apache.zookeeper.Op;
import org.apache.zookeeper.OpResult;
import org.apache.zookeeper.WatchedEvent;
import org.apache.zookeeper.Watcher;
import org.apache.zookeeper.Watcher.Event.KeeperState;
import org.apache.zookeeper.ZooDefs;
import org.apache.zookeeper.ZooKeeper;
import org.apache.zookeeper.data.ACL;
import org.apache.zookeeper.data.Stat;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * A thin wrapper around vanilla zookeeper client to facilitate the usage of
 * Parseq.
 *
 * @author Ang Xu
 */
class ZKClientImpl implements ZKClient {

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

  private volatile ZooKeeper _zkClient;
  private volatile Reaper _reaper;

  private final String _connectionString;
  private final int _sessionTimeout;
  private final Engine _engine;

  private final Watcher _defaultWatcher = new DefaultWatcher();
  private final Object _mutex = new Object();
  private final StateListener _listener = new StateListener();
  private KeeperState _currentState = null;

  /**
   * Constructs a {@link ZKClientImpl} with the given parameters.
   *
   * @param connectionString comma separated host:port pairs, each corresponding to
   *                         a zk server.
   * @param sessionTimeout   session timeout in milliseconds.
   * @param engine           a parseq {@link Engine} to schedule background tasks.
   */
  public ZKClientImpl(String connectionString, int sessionTimeout, Engine engine) {
    _connectionString = connectionString;
    _sessionTimeout = sessionTimeout;
    _engine = engine;
  }

  /**
   * {@inheritDoc}
   */
  @Override
  public Promise<Void> start() {
    AtomicBoolean committed = new AtomicBoolean(false);
    SettablePromise<Void> promise = Promises.settable();
    _listener.subscribe(KeeperState.SyncConnected,
        (Promise p) -> {
          if (committed.compareAndSet(false, true)) {
            if (p.isFailed()) {
              promise.fail(p.getError());
            } else {
              promise.done(null);
            }
          }
        }
    );

    try {
      _zkClient = new ZooKeeper(_connectionString, _sessionTimeout, _defaultWatcher);
      _reaper = new Reaper(_engine);
    } catch (IOException ex) {
      if (committed.compareAndSet(false, true)) {
        promise.fail(ex);
      }
    }
    return promise;
  }

  /**
   * {@inheritDoc}
   */
  @Override
  public void shutdown() throws InterruptedException {
    if (_zkClient != null) {
      _zkClient.close();
    }
  }

  /**
   * {@inheritDoc}
   */
  @Override
  public Task<String> create(String path, byte[] data, List<ACL> acl, CreateMode createMode) {
    return Task.async("zkCreate: " + path, () -> {
      SettablePromise<String> promise = Promises.settable();
      _zkClient.create(path, data, acl, createMode,
          (int rc, String p, Object ctx, String name) -> {
            KeeperException.Code code = KeeperException.Code.get(rc);
            switch (code) {
              case OK:
                promise.done(name);
                break;
              default:
                promise.fail(KeeperException.create(code, p));
            }
          }, null);
      return promise;
    });
  }

  /**
   * {@inheritDoc}
   */
  @Override
  public WatchableTask<ZKData> getData(String path) {
    return new WatchableTask<ZKData>("zkGetData: " + path) {
      @Override
      protected Promise<? extends ZKData> run(Context context)
          throws Throwable {
        SettablePromise<ZKData> promise = Promises.settable();

        _zkClient.getData(path, _watcher,
            (int rc, String p, Object ctx, byte[] data, Stat stat) -> {
              KeeperException.Code code = KeeperException.Code.get(rc);
              switch (code) {
                case OK:
                  _zkClient.getACL(path, stat,
                      (int rc1, String p1, Object ctx1, List<ACL> acls, Stat stat1) -> {
                        KeeperException.Code code1 = KeeperException.Code.get(rc1);
                        switch (code1) {
                          case OK:
                            promise.done(new ZKData(p, data, stat, acls));
                            break;
                          default:
                            promise.fail(KeeperException.create(code1, p1));
                        }
                      }, null);
                  break;
                default:
                  promise.fail(KeeperException.create(code, p));
              }
            }, null);
        return promise;
      }
    };
  }

  /**
   * {@inheritDoc}
   */
  @Override
  public Task<Stat> setData(String path, byte[] data, int version) {
    return Task.async("zkSetData: " + path, () -> {
      SettablePromise<Stat> promise = Promises.settable();
      _zkClient.setData(path, data, version,
          (int rc, String p, Object ctx, Stat stat) -> {
            KeeperException.Code code = KeeperException.Code.get(rc);
            switch (code) {
              case OK:
                promise.done(stat);
                break;
              default:
                promise.fail(KeeperException.create(code, p));
            }
          }, null);
      return promise;
    });
  }

  /**
   * {@inheritDoc}
   */
  @Override
  public WatchableTask<List<String>> getChildren(String path) {
    return new WatchableTask<List<String>>("zkGetChildren: " + path) {
      @Override
      protected Promise run(Context context) throws Throwable {
        SettablePromise<List<String>> promise = Promises.settable();
        _zkClient.getChildren(path, _watcher, (int rc, String p, Object ctx, List<String> children) -> {
          KeeperException.Code code = KeeperException.Code.get(rc);
          switch (code) {
            case OK:
              promise.done(children);
              break;
            default:
              promise.fail(KeeperException.create(code, p));
          }
        }, null);
        return promise;
      }
    };
  }

  /**
   * {@inheritDoc}
   */
  @Override
  public WatchableTask<Optional<Stat>> exists(String path) {
    return new WatchableTask<Optional<Stat>>("zkExists: " + path) {
      @Override
      protected Promise run(Context context) throws Throwable {
        SettablePromise<Optional<Stat>> promise = Promises.settable();
        _zkClient.exists(path, _watcher, (int rc, String p, Object ctx, Stat stat) -> {
          KeeperException.Code code = KeeperException.Code.get(rc);
          switch (code) {
            case OK:
              promise.done(Optional.of(stat));
              break;
            case NONODE:
              promise.done(Optional.empty());
              break;
            default:
              promise.fail(KeeperException.create(code, p));
          }
        }, null);
        return promise;
      }
    };
  }

  /**
   * {@inheritDoc}
   */
  @Override
  public Task<Void> delete(String path, int version) {
    return Task.async("zkDelete: " + path, () -> {
      SettablePromise<Void> promise = Promises.settable();
      _zkClient.delete(path, version, (int rc, String p, Object ctx) -> {
        KeeperException.Code code = KeeperException.Code.get(rc);
        switch (code) {
          case OK:
            promise.done(null);
            break;
          default:
            promise.fail(KeeperException.create(code, p));
        }
      }, null);
      return promise;
    });
  }

  /**
   * {@inheritDoc}
   */
  @Override
  public Task<List<OpResult>> multi(List<Op> ops, Executor executor) {
    return Task.blocking(() -> _zkClient.multi(ops), executor);
  }

  /**
   * Returns task that will wait for the given {@link KeeperState} to fulfill.
   * The task will be failed if the underlying zookeeper session expires.
   *
   * @param state keeper state to wait for.
   * @return task that waits for a certain keeper state.
   */
  public Task<Void> waitFor(KeeperState state) {
    return Task.async("waitFor " + state.toString(), () -> {
      SettablePromise<Void> promise = Promises.settable();
      synchronized (_mutex) {
        if (_currentState == state) {
          return Promises.VOID;
        } else {
          _listener.subscribe(state, (p) -> {
            if (p.isFailed()) {
              promise.fail(p.getError());
            } else {
              promise.done(null);
            }
          });
        }
      }
      return promise;
    });
  }

  /**
   * {@inheritDoc}
   */
  @Override
  public Task<Void> waitFor(KeeperState state, long deadline) {
    return waitFor(state).withTimeout(deadline - System.currentTimeMillis(), TimeUnit.MILLISECONDS);
  }

  /**
   * {@inheritDoc}
   */
  @Override
  public Task<String> ensurePathExists(String path) {
    return exists(path).flatMap(stat -> {
      if (!stat.isPresent()) {
        Task<String> createIfAbsent = create(path, null, ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.PERSISTENT)
            .recoverWith("recover from NodeExistsException", e -> {
              if (e instanceof KeeperException.NodeExistsException) {
                return Task.value(path);
              } else {
                return Task.failure(e);
              }
            });
        String parent = path.substring(0, path.lastIndexOf('/'));
        if (parent.isEmpty()) { // parent is root
          return createIfAbsent;
        } else {
          return ensurePathExists(parent).flatMap(unused -> createIfAbsent);
        }
      } else {
        return Task.value(path);
      }
    });
  }

  /**
   * {@inheritDoc}
   */
  @Override
  public void deleteNode(String node) {
    _reaper.submit(() -> delete(node, -1));
  }

  /**
   * {@inheritDoc}
   */
  @Override
  public void deleteNodeHasUUID(String path, String uuid) {
    _reaper.submit(() -> getChildren(path).map(children -> ZKUtil.findNodeWithUUID(children, uuid)).flatMap(node -> {
      if (node != null) {
        return delete(node, -1);
      } else {
        return Task.value(null);
      }
    }));
  }

  /**
   * {@inheritDoc}
   */
  @Override
  public void addAuthInfo(String scheme, byte[] auth) {
    _zkClient.addAuthInfo(scheme, auth);
  }

  /**
   * {@inheritDoc}
   */
  @Override
  public Task<Stat> setACL(String path, List<ACL> acls, int version) {
    return Task.async("zkSetACL: " + path, () -> {
      SettablePromise<Stat> promise = Promises.settable();
      _zkClient.setACL(path, acls, version,
          (int rc, String p, Object ctx, Stat stat) -> {
            KeeperException.Code code = KeeperException.Code.get(rc);
            switch (code) {
              case OK:
                promise.done(stat);
                break;
              default:
                promise.fail(KeeperException.create(code, p));
            }
          }, null);
      return promise;
    });
  }

  private class DefaultWatcher implements Watcher {
    @Override
    public void process(WatchedEvent watchedEvent) {
      if (watchedEvent.getType() == Event.EventType.None) {
        Event.KeeperState state = watchedEvent.getState();
        switch (state) {
          case Disconnected:
          case SyncConnected:
          case AuthFailed:
          case ConnectedReadOnly:
          case SaslAuthenticated:
            synchronized (_mutex) {
              _currentState = state;
              _listener.done(state);
            }
            break;
          case Expired:
            synchronized (_mutex) {
              _currentState = state;
              for (KeeperState ks : KeeperState.values()) {
                if (ks == KeeperState.Expired) {
                  _listener.done(KeeperState.Expired);
                } else {
                  _listener.fail(ks, KeeperException.create(KeeperException.Code.SESSIONEXPIRED));
                }
              }
            }
            break;
          default:
            LOG.warn("Received unknown state {} for session 0x{}", state, Long.toHexString(_zkClient.getSessionId()));
        }
      }
    }
  }

  /**
   * A collection of listeners listen to Zookeeper {@link KeeperState state}.
   */
  private static class StateListener {
    private final Map<KeeperState, SettablePromise<Void>> _listeners;

    public StateListener() {
      _listeners = new EnumMap<>(KeeperState.class);
      for (KeeperState state : KeeperState.values()) {
        _listeners.put(state, Promises.settable());
      }
    }

    public void done(KeeperState state) {
      SettablePromise promise = _listeners.put(state, Promises.settable());
      promise.done(null);
    }

    public void fail(KeeperState state, Exception e) {
      SettablePromise promise = _listeners.put(state, Promises.settable());
      promise.fail(e);
    }

    public void subscribe(KeeperState state, PromiseListener listener) {
      _listeners.get(state).addListener(listener);
    }

  }
}


package com.linkedin.parseq.trace;

import java.io.IOException;
import java.util.Collections;
import java.util.Map;
import java.util.Set;

public class Trace {

  private final Map<Long, ShallowTrace> _traceMap;
  private final Set<TraceRelationship> _relationships;
  private final Long _planId;
  private final String _planClass;

  // TODO: this constructor should be removed.
  // Need to fix in the next major version release.
  public Trace(Map<Long, ShallowTrace> traceMap, Set<TraceRelationship> relationships) {
    this(traceMap, relationships, TraceBuilder.UNKNOWN_PLAN_CLASS, TraceBuilder.UNKNOWN_PLAN_ID);
  }

  public Trace(Map<Long, ShallowTrace> traceMap, Set<TraceRelationship> relationships, String planClass, Long planId) {
    _traceMap = Collections.unmodifiableMap(traceMap);
    _relationships = Collections.unmodifiableSet(relationships);
    _planClass = planClass;
    _planId = planId;
  }

  public Map<Long, ShallowTrace> getTraceMap() {
    return _traceMap;
  }

  public Set<TraceRelationship> getRelationships() {
    return _relationships;
  }

  // TODO: this method should be removed.
  // Need to fix in the next major version release.
  public static Trace single(ShallowTrace shallowTrace) {
    return single(shallowTrace, TraceBuilder.UNKNOWN_PLAN_CLASS, TraceBuilder.UNKNOWN_PLAN_ID);
  }

  public static Trace single(ShallowTrace shallowTrace, String planClass, Long planId) {
    return new Trace(Collections.singletonMap(shallowTrace.getId(), shallowTrace), Collections.emptySet(), planClass, planId);
  }

  public Long getPlanId() {
    return _planId;
  }

  public String getPlanClass() {
    return _planClass;
  }

  @Override
  public int hashCode() {
    final int prime = 31;
    int result = 1;
    result = prime * result + ((_relationships == null) ? 0 : _relationships.hashCode());
    result = prime * result + ((_traceMap == null) ? 0 : _traceMap.hashCode());
    return result;
  }

  @Override
  public boolean equals(Object obj) {
    if (this == obj)
      return true;
    if (obj == null)
      return false;
    if (getClass() != obj.getClass())
      return false;
    Trace other = (Trace) obj;
    if (_relationships == null) {
      if (other._relationships != null)
        return false;
    } else if (!_relationships.equals(other._relationships))
      return false;
    if (_traceMap == null) {
      if (other._traceMap != null)
        return false;
    } else if (!_traceMap.equals(other._traceMap))
      return false;
    return true;
  }

  @Override
  public String toString() {
    try {
      return TraceUtil.getJsonTrace(this);
    } catch (IOException e) {
      throw new RuntimeException("Could not serialize trace to JSON", e);
    }
  }

}


package com.linkedin.restli.client.config;

import java.util.HashMap;
import java.util.Map;
import java.util.Optional;
import java.util.function.Function;

import com.linkedin.restli.common.ResourceMethod;

class RequestConfigTree<T> {

  /**
   * Priorities:
   * 1. outbound name
   * 2. inbound name
   * 3. outbound operation
   * 4. outbound operation name
   * 5. inbound operation
   * 6. inbound operation name
   */
  private final Map<Optional<String>, Map<Optional<String>, Map<Optional<ResourceMethod>, Map<Optional<String>, Map<Optional<String>, Map<Optional<String>, ConfigValue<T>>>>>>> _tree =
      new HashMap<>();

  @SuppressWarnings("unchecked")
  void add(RequestConfigElement element) {
    _tree.computeIfAbsent(element.getOutboundName(), k -> new HashMap<>())
         .computeIfAbsent(element.getInboundName(), k -> new HashMap<>())
         .computeIfAbsent(element.getOutboundOp(), k -> new HashMap<>())
         .computeIfAbsent(element.getOutboundOpName(), k -> new HashMap<>())
         .computeIfAbsent(element.getInboundOp(), k -> new HashMap<>())
         .putIfAbsent(element.getInboundOpName(), new ConfigValue<>((T)element.getValue(), element.getKey()));
  }

  Optional<ConfigValue<T>> resolveInboundOpName(RequestConfigCacheKey cacheKeyd,
     Map<Optional<String>, ConfigValue<T>> map) {
    if (map != null) {
      Optional<String> inboundOpName = cacheKeyd.getInboundOpName();
      if (inboundOpName.isPresent()) {
        ConfigValue<T> value = map.get(inboundOpName);
        if (value != null) {
          return Optional.of(value);
        }
      }
      return Optional.ofNullable(map.get(Optional.empty()));
    } else {
      return Optional.empty();
    }
  }

  Optional<ConfigValue<T>> resolveInboundOp(RequestConfigCacheKey cacheKeyd,
      Map<Optional<String>, Map<Optional<String>, ConfigValue<T>>> map) {
    if (map != null) {
      Optional<String> inboundOp = cacheKeyd.getInboundOp();
      if (inboundOp.isPresent()) {
        Optional<ConfigValue<T>> value = resolveInboundOpName(cacheKeyd, map.get(inboundOp));
        if (value.isPresent()) {
          return value;
        }
      }
      return resolveInboundOpName(cacheKeyd, map.get(Optional.empty()));
    } else {
      return Optional.empty();
    }
  }

  Optional<ConfigValue<T>> resolveOutboundOpName(RequestConfigCacheKey cacheKeyd,
      Map<Optional<String>, Map<Optional<String>, Map<Optional<String>, ConfigValue<T>>>> map) {
    if (map != null) {
      Optional<String> outboundOpName = cacheKeyd.getOutboundOpName();
      if (outboundOpName.isPresent()) {
        Optional<ConfigValue<T>> value = resolveInboundOp(cacheKeyd, map.get(outboundOpName));
        if (value.isPresent()) {
          return value;
        }
      }
      return resolveInboundOp(cacheKeyd, map.get(Optional.empty()));
    } else {
      return Optional.empty();
    }
  }

  Optional<ConfigValue<T>> resolveOutboundOp(RequestConfigCacheKey cacheKeyd,
      Map<Optional<ResourceMethod>, Map<Optional<String>, Map<Optional<String>, Map<Optional<String>, ConfigValue<T>>>>> map) {
    if (map != null) {
      Optional<ResourceMethod> outboundOp = Optional.of(cacheKeyd.getOutboundOp());
      if (outboundOp.isPresent()) {
        Optional<ConfigValue<T>> value = resolveOutboundOpName(cacheKeyd, map.get(outboundOp));
        if (value.isPresent()) {
          return value;
        }
      }
      return resolveOutboundOpName(cacheKeyd, map.get(Optional.empty()));
    } else {
      return Optional.empty();
    }
  }

  /**
   * This method recursively uses given resolver to resolve a config by given name taking into account
   * syntax of sub-resource names. For example, for given name: Optional.of("foo:bar:baz") it will make
   * the following resolver calls:
   * - resolver(Optional.of("foo:bar:baz"))
   * - resolver(Optional.of("foo:bar"))
   * - resolver(Optional.of("foo"))
   * - resolver(Optional.empty())
   */
  Optional<ConfigValue<T>> resolveNameRecursively(Optional<String> name, Function<Optional<String>, Optional<ConfigValue<T>>> resolver) {
    Optional<ConfigValue<T>> value = resolver.apply(name);
    if (value.isPresent()) {
      return value;
    } else {
      if (name.isPresent()) {
        return resolveNameRecursively(name.filter(s -> s.lastIndexOf(':') > 0).map(s -> s.substring(0, s.lastIndexOf(':'))), resolver);
      } else {
        return Optional.empty();
      }
    }
  }

  Optional<ConfigValue<T>> resolveInboundName(RequestConfigCacheKey cacheKeyd,
      Map<Optional<String>, Map<Optional<ResourceMethod>, Map<Optional<String>, Map<Optional<String>, Map<Optional<String>, ConfigValue<T>>>>>> map) {
    if (map != null) {
      return resolveNameRecursively(cacheKeyd.getInboundName(), x -> resolveOutboundOp(cacheKeyd, map.get(x)));
    } else {
      return Optional.empty();
    }
  }

  Optional<ConfigValue<T>> resolveOutboundName(RequestConfigCacheKey cacheKeyd) {
    return resolveNameRecursively(Optional.of(cacheKeyd.getOutboundName()), x -> resolveInboundName(cacheKeyd, _tree.get(x)));
  }

  ConfigValue<T> resolve(RequestConfigCacheKey cacheKey) {
    return resolveOutboundName(cacheKey).get();
  }
}


/*
 * Copyright 2016 LinkedIn Corp.
 *
 * 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.linkedin.parseq.zk.recipes;

import java.net.InetAddress;
import java.net.UnknownHostException;
import java.nio.charset.Charset;

/**
 * @author Ang Xu
 */
public final class LockUtil {

  private static final Charset UTF8 = Charset.forName("UTF-8");
  private static final String HOST_NAME;

  static {
    String host;
    try {
      host = InetAddress.getLocalHost().getHostName();
    } catch (UnknownHostException e) {
      host = "Unknown Host";
    }
    HOST_NAME = host;
  }

  private LockUtil() {
  }

  public static String getNodeName(String path) {
    int index = path.lastIndexOf('/');
    return path.substring(index+1);
  }

  /**
   * Returns lock data in a form of "hostname:threadName"
   */
  public static byte[] getLockData() {
    String str = String.format("%s:%s", HOST_NAME, Thread.currentThread().getName());
    return str.getBytes(UTF8);
  }
}


package com.linkedin.parseq.batching;

import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;

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

import com.linkedin.parseq.Context;
import com.linkedin.parseq.Task;
import com.linkedin.parseq.batching.BatchImpl.BatchBuilder;
import com.linkedin.parseq.batching.BatchImpl.BatchEntry;
import com.linkedin.parseq.batching.BatchImpl.BatchPromise;
import com.linkedin.parseq.internal.ContextImpl;
import com.linkedin.parseq.internal.PlanContext;
import com.linkedin.parseq.promise.CountDownPromiseListener;
import com.linkedin.parseq.promise.PromiseListener;
import com.linkedin.parseq.promise.Promises;
import com.linkedin.parseq.promise.SettablePromise;
import com.linkedin.parseq.trace.Relationship;
import com.linkedin.parseq.trace.ShallowTraceBuilder;
import com.linkedin.parseq.trace.TraceBuilder;

/**
 * {@code BatchingStrategy} helps build "batching clients" in ParSeq. "Client" means an object that given {@code K key}
 * provides a task that returns {@code T value}. "Batching" means that it can group together keys to resolve values
 * in batches. The benefit of this approach is that batching happens transparently in the background and user's code
 * does not have to deal with logic needed to implement batching.
 *

 * Example of a batching client might be ParSeq client for a key-value store that provides batch get operation. For
 * the sake of simplicity of the example we are using dummy, synchronous key-value store interface:
 *

 *  interface KVStore {
 *    String get(Long key);
 *    Map{@code <Long, String>} batchGet(Collection{@code <Long>} keys);
 *  }
 *

 *
 * We can then implement a {@code BatchingStrategy} in the following way:
 *

 *  public static class BatchingKVStoreClient extends BatchingStrategy{@code <Integer, Long, String>} {
 *    private final KVStore _store;
 *    public BatchingKVStoreClient(KVStore store) {
 *      _store = store;
 *    }
 *
 *    {@code @Override}
 *    public void executeBatch(Integer group, Batch{@code <Long, String>} batch) {
 *      Map{@code <Long, String>} batchResult = _store.batchGet(batch.keys());
 *      batch.foreach((key, promise) {@code ->} promise.done(batchResult.get(key)));
 *    }
 *
 *    {@code @Override}
 *    public Integer classify(Long entry) {
 *      return 0;
 *    }
 *  }
 *

 *
 * In above example there is an assumption that all keys can be grouped together. This is why method {@code classify()}
 * trivially returns a constant {@code 0}. In practice {@code classify()} returns a group for a key. Keys that have
 * the same group will be batched together.
 *

 * The interaction between ParSeq and {@code BatchingStrategy} is the following:
 *

 *
{@code batchable(String desc, K key)} is invoked to create Task instance

 *
Plan is started by {@code Engine.run()}

 *
When Task returned by {@code batchable(String desc, K key)} is started, the key {@code K} is remembered by a {@code BatchingStrategy}

 *
When Plan can't make immediate progress {@code BatchingStrategy} will be invoked to run batchable operations:
 *

 *
Every {@code K key} is classified using {@code classify(K key)} method

 *
Keys, together with adequate Promises, are batched together based on {@code G group} returned by previous step

 *
Method {@code executeBatch(G group, Batch<K, T> batch)} is invoked for every batch

 *

 *   {@code executeBatch(G group, Batch<K, T> batch)} invocations are executed
 *   in the context of their own Task instances with description given by {@code getBatchName(G group, Batch<K, T> batch)}.
 *   Implementation of {@code BatchingStrategy} has to be fast because it is executed sequentially with respect to tasks belonging
 *   to the plan. It means that no other task will be executed until {@code BatchingStrategy} completes. Typically classify(K key)
 *   is a synchronous and fast operation whilst {@code executeBatch(G group, Batch<K, T> batch)} returns quickly and completes
 *   promises asynchronously.
 *

 *
 * @author Jaroslaw Odzga (jodzga@linkedin.com)
 *
 * @param <G> Type of a Group
 * @param <K> Type of a Key
 * @param <T> Type of a Value
 *
 * @see SimpleBatchingStrategy
 * @see TaskBatchingStrategy
 */
public abstract class BatchingStrategy<G, K, T> {

  public static final int DEFAULT_MAX_BATCH_SIZE = 1024;

  private static final Logger LOGGER = LoggerFactory.getLogger(BatchingStrategy.class);
  private static final int DEFAULT_KEY_SIZE = 1;

  private final ConcurrentMap<Long, GroupBatchBuilder> _batches =
      new ConcurrentHashMap<>();

  private final BatchSizeMetric _batchSizeMetric = new BatchSizeMetric();
  private final BatchAggregationTimeMetric _batchAggregationTimeMetric = new BatchAggregationTimeMetric();

  /**
   * This method returns Task that returns value for a single key allowing this strategy to batch operations.
   * @param desc description of the task
   * @param key key
   * @return Task that returns value for a single key allowing this strategy to batch operations
   */
  public Task<T> batchable(final String desc, final K key) {
    Task<T> batchableTask = Task.async(desc, ctx -> {
      final BatchPromise<T> result = new BatchPromise<>();
      final Long planId = ctx.getPlanId();
      final GroupBatchBuilder builder = _batches.computeIfAbsent(planId, k -> new GroupBatchBuilder());
      final G group = classify(key);
      Batch<K, T> fullBatch = builder.add(group, key, ctx.getShallowTraceBuilder(), result);
      if (fullBatch != null) {
        try {
          ctx.run(taskForBatch(group, fullBatch, true));
        } catch (Throwable t) {
          //we don't care if some of promises have already been completed
          //all we care is that all remaining promises have been failed
          fullBatch.failAll(t);
        }
      }
      return result;
    });
    batchableTask.getShallowTraceBuilder().setTaskType("batched");
    return batchableTask;
  }

  /**
   * This method returns Task that returns value for a single key allowing this strategy to batch operations.
   * @param key key
   * @return Task that returns value for a single key allowing this strategy to batch operations
   */
  public Task<T> batchable(final K key) {
    return batchable("batchableTaskForKey: " + key.toString(), key);
  }

  private Task<?> taskForBatch(final G group, final Batch<K, T> batch, final boolean hasParent) {
    _batchSizeMetric.record(batch.batchSize());
    if (LOGGER.isDebugEnabled()) {
      LOGGER.debug(debugInfo(group, batch));
    }
    return Task.async(getBatchName(group, batch), ctx -> {
      final SettablePromise<T> result = Promises.settable();
      final PromiseListener<T> countDownListener =
          new CountDownPromiseListener<>(batch.keySize(), result, null);

      boolean assignedParent = false;
      final TraceBuilder traceBuilder = ctx.getTraceBuilder();
      for (BatchEntry<T> entry : batch.values()) {
        for (ShallowTraceBuilder shallowTraceBuilder: entry.getShallowTraceBuilders()) {
          if (!assignedParent && !hasParent) {
            traceBuilder.addRelationship(Relationship.CHILD_OF, ctx.getShallowTraceBuilder(), shallowTraceBuilder);
            assignedParent = true;
          } else {
            traceBuilder.addRelationship(Relationship.POTENTIAL_CHILD_OF, ctx.getShallowTraceBuilder(), shallowTraceBuilder);
          }
        }
        BatchPromise<T> promise = entry.getPromise();
        promise.getInternal().addListener(countDownListener);
        result.addListener(v -> promise.trigger());
      }

      try {
        executeBatchWithContext(group, batch, ctx);
      } catch (Throwable t) {
        batch.failAll(t);
      }

      ctx.getShallowTraceBuilder().setSystemHidden(true);

      return result;
    });
  }

  private void runBatch(final PlanContext planContext, G group, final Batch<K, T> batch) {
    try {
      Task<?> batchedTask = taskForBatch(group, batch, false);
      PlanContext forkedPlan = planContext.fork(batchedTask);
      new ContextImpl(forkedPlan, batchedTask).runTask();
    } catch (Throwable t) {
      //we don't care if some of promises have already been completed
      //all we care is that all remaining promises have been failed
      batch.failAll(t);
    }
  }

  void handleBatch(final PlanContext planContext) {
    final GroupBatchBuilder batchBuilder = _batches.remove(planContext.getId());
    if (batchBuilder != null) {
      batchBuilder.batches().forEach((group, builder) -> runBatch(planContext, group, builder.build()));
    }
  }

  private String debugInfo(G group, Batch<K, T> batch) {
    StringBuilder debugInfo = new StringBuilder("\n");
      debugInfo.append("group: ")
        .append(group)
        .append("\n")
        .append("batch keys: \n");
      batch.keys().forEach(key -> debugInfo.append("    ").append(key).append("\n"));
    return debugInfo.toString();
  }

  public BatchSizeMetric getBatchSizeMetric() {
    return _batchSizeMetric;
  }

  public BatchAggregationTimeMetric getBatchAggregationTimeMetric() {
    return _batchAggregationTimeMetric;
  }

  /**
   * This method will be called for every {@code Batch}.
   * Implementation of this method must make sure that all {@code SettablePromise} contained in the {@code Batch}
   * will eventually be resolved - typically asynchronously. Failing to eventually resolve any
   * of the promises may lead to plan that never completes i.e. appears to hung and may lead to
   * a memory leak.
   * @param group group that represents the batch
   * @param batch batch contains collection of {@code SettablePromise} that eventually need to be resolved - typically asynchronously
   */
  public abstract void executeBatch(G group, Batch<K, T> batch);

  protected void executeBatchWithContext(G group, Batch<K, T> batch, Context ctx) {
    executeBatch(group, batch);
  }

  /**
   * Classify the {@code K Key} and by doing so assign it to a {@code G group}.
   * If two keys are classified by the same group then they will belong to the same {@code Batch}.
   * This method needs to be thread safe.
   * @param key key to be classified
   * @return Group that represents a batch the key will belong to
   */
  public abstract G classify(K key);

  /**
   * Overriding this method allows specifying maximum batch size for a given group.
   * Default value is {@value #DEFAULT_MAX_BATCH_SIZE}.
   * @param group group for which maximum batch size needs to be decided
   * @return maximum batch size for a given group
   */
  public int maxBatchSizeForGroup(G group) {
    return DEFAULT_MAX_BATCH_SIZE;
  }

  /**
   * Overriding this method allows specifying size of the key for a given group.
   * Default value is 1. This method is used when calculating batch size and making sure
   * that it does not exceed max batch size for a group.
   * @param group group
   * @return max batch size for this group
   * @see #maxBatchSizeForGroup(Object)
   */
  public int keySize(G group, K key) {
    return DEFAULT_KEY_SIZE;
  }

  /**
   * Overriding this method allows providing custom name for a batch. Name will appear in the
   * ParSeq trace as a description of the task that executes the batch.
   * @param batch batch to be described
   * @param group group to be described
   * @return name for the batch and group
   */
  public String getBatchName(G group, Batch<K, T> batch) {
    return "batch(keys: " + batch.keySize() + ", size: " + batch.batchSize() + ")";
  }

  private class GroupBatchBuilder {
    private final Map<G, BatchBuilder<K, T>> _batchesByGroup =
        new HashMap<>();

    /**
     * Adds new entry to a batch specified by a given group and returns
     * list of batches that can be executed or null if batch is still not full.
     * @return list of batches that can be executed or null otherwise
     */
    Batch<K, T> add(G group, K key, ShallowTraceBuilder traceBuilder, BatchPromise<T> promise) {
      final int size = keySize(group, key);
      BatchBuilder<K, T> builder =
        _batchesByGroup.computeIfAbsent(group, x -> new BatchBuilder<>(maxBatchSizeForGroup(group), _batchAggregationTimeMetric));
      //invariant: builder is not full - it is maintained by the fact that max batch size >= 1
      //and that we remove builder from the map after adding to it entry that makes it full
      if (builder.add(key, traceBuilder, promise, size)) {
        if (builder.isFull()) {
          _batchesByGroup.remove(group);
          return builder.build();
        } else {
          return null;
        }
      } else {
        BatchBuilder<K, T> newBuilder = new BatchBuilder<>(maxBatchSizeForGroup(group), _batchAggregationTimeMetric);
        //this will be successful because builder is empty and first add is always successful as per builder contract
        newBuilder.add(key, traceBuilder, promise, size);
        if (newBuilder.isFull()) {
          return newBuilder.build();
        } else {
          //return larger batch
          if (builder.batchSize() > newBuilder.batchSize()) {
            _batchesByGroup.put(group, newBuilder);
            return builder.build();
          } else {
            return newBuilder.build();
          }
        }
      }
    }

    Map<G, BatchBuilder<K, T>> batches() {
      return _batchesByGroup;
    }

  }

}


package com.linkedin.parseq.batching;

import java.util.concurrent.TimeUnit;
import java.util.function.Function;

import org.HdrHistogram.Histogram;
import org.HdrHistogram.Recorder;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class BatchAggregationTimeMetric {

  private static final Logger LOGGER = LoggerFactory.getLogger(BatchAggregationTimeMetric.class);

  private static final long LOWEST_DISCERNIBLE_VALUE = 1;
  private static final long HIGHEST_TRACKABLE_VALUE = TimeUnit.HOURS.toNanos(1);
  private static final int NUMBER_OF_FIGNIFICANT_VALUE_DIGITS = 3;

  private Recorder _recorder = null;

  private Histogram _recycle;

  /**
   * Records a batch aggregation time.
   * This method is thread safe.
   * @param batchAggregationTimeNano batch aggregation time
   */
  public void record(long batchAggregationTimeNano) {
    recordSafeValue(narrow(batchAggregationTimeNano));
  }

  private long narrow(long batchAggregationTimeNano) {
    if (batchAggregationTimeNano < LOWEST_DISCERNIBLE_VALUE) {
      LOGGER.warn("batch aggregation time lower than expected: {}, recording as: {}", batchAggregationTimeNano, LOWEST_DISCERNIBLE_VALUE);
      return LOWEST_DISCERNIBLE_VALUE;
    }
    if (batchAggregationTimeNano > HIGHEST_TRACKABLE_VALUE) {
      LOGGER.warn("batch aggregation time greater than expected: {}, recording as: {}", batchAggregationTimeNano, HIGHEST_TRACKABLE_VALUE);
      return HIGHEST_TRACKABLE_VALUE;
    }
    return batchAggregationTimeNano;
  }

  private void initializeRecorder() {
    if (_recorder == null) {
      _recorder = new Recorder(LOWEST_DISCERNIBLE_VALUE, HIGHEST_TRACKABLE_VALUE, NUMBER_OF_FIGNIFICANT_VALUE_DIGITS);
    }
  }

  private synchronized void recordSafeValue(long batchAggregationTimeNano) {
    initializeRecorder();
    _recorder.recordValue(batchAggregationTimeNano);
  }

  /**
   * Allows consuming histogram and returning a result.
   * Histogram passed to the consumer includes stable, consistent view
   * of all values accumulated since last harvest.
   * This method is thread safe.
   * @param consumer consumer for a harvested histogram
   * @param <T> return type of a passed in function
   * @return a result of a passed in function
   */
  public synchronized <T> T harvest(Function<Histogram, T> consumer) {
    initializeRecorder();
    _recycle = _recorder.getIntervalHistogram(_recycle);
    return consumer.apply(_recycle);
  }
}


package com.linkedin.parseq;

import java.util.Collection;
import java.util.concurrent.TimeUnit;
import java.util.function.Function;

import com.linkedin.parseq.function.Tuple6;
import com.linkedin.parseq.promise.PromiseException;
import com.linkedin.parseq.promise.PromiseListener;
import com.linkedin.parseq.promise.PromiseUnresolvedException;
import com.linkedin.parseq.trace.ShallowTrace;
import com.linkedin.parseq.trace.ShallowTraceBuilder;
import com.linkedin.parseq.trace.Trace;
import com.linkedin.parseq.trace.TraceBuilder;

public class Tuple6TaskDelegate<T1, T2, T3, T4, T5, T6> implements Tuple6Task<T1, T2, T3, T4, T5, T6> {

  private final Task<Tuple6<T1, T2, T3, T4, T5, T6>> _task;

  public Tuple6TaskDelegate(Task<Tuple6<T1, T2, T3, T4, T5, T6>> task) {
    _task = task;
  }

  /**
   * {@inheritDoc}
   */
  @Override
  public boolean cancel(Exception reason) {
    return _task.cancel(reason);
  }

  /**
   * {@inheritDoc}
   */
  @Override
  public Tuple6<T1, T2, T3, T4, T5, T6> get() throws PromiseException {
    return _task.get();
  }

  /**
   * {@inheritDoc}
   */
  @Override
  public Throwable getError() throws PromiseUnresolvedException {
    return _task.getError();
  }

  /**
   * {@inheritDoc}
   */
  @Override
  public Tuple6<T1, T2, T3, T4, T5, T6> getOrDefault(Tuple6<T1, T2, T3, T4, T5, T6> defaultValue) throws PromiseUnresolvedException {
    return _task.getOrDefault(defaultValue);
  }

  /**
   * {@inheritDoc}
   */
  @Override
  public String getName() {
    return _task.getName();
  }

  /**
   * {@inheritDoc}
   */
  @Override
  public int getPriority() {
    return _task.getPriority();
  }

  /**
   * {@inheritDoc}
   */
  @Override
  public boolean setPriority(int priority) {
    return _task.setPriority(priority);
  }

  /**
   * {@inheritDoc}
   */
  @Override
  public void await() throws InterruptedException {
    _task.await();
  }

  /**
   * {@inheritDoc}
   */
  @Override
  public void contextRun(Context context, Task<?> parent, Collection<Task<?>> predecessors) {
    _task.contextRun(context, parent, predecessors);
  }

  /**
   * {@inheritDoc}
   */
  @Override
  public boolean await(long time, TimeUnit unit) throws InterruptedException {
    return _task.await(time, unit);
  }

  /**
   * {@inheritDoc}
   */
  @Override
  public ShallowTrace getShallowTrace() {
    return _task.getShallowTrace();
  }

  /**
   * {@inheritDoc}
   */
  @Override
  public void addListener(PromiseListener<Tuple6<T1, T2, T3, T4, T5, T6>> listener) {
    _task.addListener(listener);
  }

  /**
   * {@inheritDoc}
   */
  @Override
  public Trace getTrace() {
    return _task.getTrace();
  }

  /**
   * {@inheritDoc}
   */
  @Override
  public boolean isDone() {
    return _task.isDone();
  }

  /**
   * {@inheritDoc}
   */
  @Override
  public boolean isFailed() {
    return _task.isFailed();
  }

  /**
   * {@inheritDoc}
   */
  @Override
  public Long getId() {
    return _task.getId();
  }

  @Override
  public void setTraceValueSerializer(Function<Tuple6<T1, T2, T3, T4, T5, T6>, String> serializer) {
    _task.setTraceValueSerializer(serializer);
  }

  @Override
  public ShallowTraceBuilder getShallowTraceBuilder() {
    return _task.getShallowTraceBuilder();
  }

  @Override
  public TraceBuilder getTraceBuilder() {
    return _task.getTraceBuilder();
  }

}


package com.linkedin.parseq;

import java.util.Collection;
import java.util.concurrent.TimeUnit;
import java.util.function.Function;

import com.linkedin.parseq.function.Tuple13;
import com.linkedin.parseq.promise.PromiseException;
import com.linkedin.parseq.promise.PromiseListener;
import com.linkedin.parseq.promise.PromiseUnresolvedException;
import com.linkedin.parseq.trace.ShallowTrace;
import com.linkedin.parseq.trace.ShallowTraceBuilder;
import com.linkedin.parseq.trace.Trace;
import com.linkedin.parseq.trace.TraceBuilder;

public class Tuple13TaskDelegate<T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13> implements Tuple13Task<T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13> {

  private final Task<Tuple13<T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13>> _task;

  public Tuple13TaskDelegate(Task<Tuple13<T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13>> task) {
    _task = task;
  }

  /**
   * {@inheritDoc}
   */
  @Override
  public boolean cancel(Exception reason) {
    return _task.cancel(reason);
  }

  /**
   * {@inheritDoc}
   */
  @Override
  public Tuple13<T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13> get() throws PromiseException {
    return _task.get();
  }

  /**
   * {@inheritDoc}
   */
  @Override
  public Throwable getError() throws PromiseUnresolvedException {
    return _task.getError();
  }

  /**
   * {@inheritDoc}
   */
  @Override
  public Tuple13<T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13> getOrDefault(Tuple13<T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13> defaultValue) throws PromiseUnresolvedException {
    return _task.getOrDefault(defaultValue);
  }

  /**
   * {@inheritDoc}
   */
  @Override
  public String getName() {
    return _task.getName();
  }

  /**
   * {@inheritDoc}
   */
  @Override
  public int getPriority() {
    return _task.getPriority();
  }

  /**
   * {@inheritDoc}
   */
  @Override
  public boolean setPriority(int priority) {
    return _task.setPriority(priority);
  }

  /**
   * {@inheritDoc}
   */
  @Override
  public void await() throws InterruptedException {
    _task.await();
  }

  /**
   * {@inheritDoc}
   */
  @Override
  public void contextRun(Context context, Task<?> parent, Collection<Task<?>> predecessors) {
    _task.contextRun(context, parent, predecessors);
  }

  /**
   * {@inheritDoc}
   */
  @Override
  public boolean await(long time, TimeUnit unit) throws InterruptedException {
    return _task.await(time, unit);
  }

  /**
   * {@inheritDoc}
   */
  @Override
  public ShallowTrace getShallowTrace() {
    return _task.getShallowTrace();
  }

  /**
   * {@inheritDoc}
   */
  @Override
  public void addListener(PromiseListener<Tuple13<T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13>> listener) {
    _task.addListener(listener);
  }

  /**
   * {@inheritDoc}
   */
  @Override
  public Trace getTrace() {
    return _task.getTrace();
  }

  /**
   * {@inheritDoc}
   */
  @Override
  public boolean isDone() {
    return _task.isDone();
  }

  /**
   * {@inheritDoc}
   */
  @Override
  public boolean isFailed() {
    return _task.isFailed();
  }

  /**
   * {@inheritDoc}
   */
  @Override
  public Long getId() {
    return _task.getId();
  }

  @Override
  public void setTraceValueSerializer(Function<Tuple13<T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13>, String> serializer) {
    _task.setTraceValueSerializer(serializer);
  }

  @Override
  public ShallowTraceBuilder getShallowTraceBuilder() {
    return _task.getShallowTraceBuilder();
  }

  @Override
  public TraceBuilder getTraceBuilder() {
    return _task.getTraceBuilder();
  }

}


package com.linkedin.restli.client.config;

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;

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

import com.linkedin.restli.client.InboundRequestContextFinder;
import com.linkedin.restli.client.ParSeqRestliClientConfig;
import com.linkedin.restli.client.ParSeqRestliClientConfigBuilder;
import com.linkedin.restli.client.Request;

class RequestConfigProviderImpl implements RequestConfigProvider {

  private static final Logger LOGGER = LoggerFactory.getLogger(RequestConfigProviderImpl.class);

  static final int DEFAULT_MAX_BATCH_SIZE = 1024;
  static final int CONFIG_CACHE_SIZE = 4096;
  static final Boolean DEFAULT_BATCHING_ENABLED = Boolean.FALSE;
  static final long DEFAULT_TIMEOUT = 0L;

  static final ParSeqRestliClientConfig DEFAULT_CONFIG = createDefaultConfig();

  private final InboundRequestContextFinder _inboundRequestContextFinder;
  private final RequestConfigTree<Long> _timeoutMs = new RequestConfigTree<>();
  private final RequestConfigTree<Boolean> _batchingEnabled = new RequestConfigTree<>();
  private final RequestConfigTree<Integer> _maxBatchSize = new RequestConfigTree<>();
  private final ConcurrentMap<RequestConfigCacheKey, RequestConfig> _cache = new ConcurrentHashMap<>();

  public RequestConfigProviderImpl(InboundRequestContextFinder inboundRequestContextFinder, ParSeqRestliClientConfig config) throws RequestConfigKeyParsingException {
    _inboundRequestContextFinder = inboundRequestContextFinder;
    initialize(config);
  }

  private void initialize(ParSeqRestliClientConfig config) throws RequestConfigKeyParsingException {
    boolean failed = initializeProperty(config.getTimeoutMsConfig(), "timeoutMs") ||
                     initializeProperty(config.isBatchingEnabledConfig(), "batchingEnabled") ||
                     initializeProperty(config.getMaxBatchSizeConfig(), "maxBatchSize");
    if (failed) {
      throw new RequestConfigKeyParsingException("Configuration parsing error, see log file for details.");
    }
  }

  private boolean initializeProperty(Map<String, ?> config, String property) {
    boolean failed = false;
    List<RequestConfigElement> elements = new ArrayList<>();
    for (Map.Entry<String, ?> entry: config.entrySet()) {
      try {
        RequestConfigElement element = RequestConfigElement.parse(property, entry.getKey(), entry.getValue());
        processConfigElement(element);
        elements.add(element);
      } catch (RequestConfigKeyParsingException e) {
        LOGGER.error("Configuration parsing error", e);
        failed = true;
      }
    }
    if (!failed) {
      Collections.sort(elements);
      StringBuilder sb = new StringBuilder();
      sb.append("ParSeq RestLi Client Configuration for property " + property + " sorted by priority - first match gets applied:\n");
      elements.forEach(el -> sb.append(el.getKey())
                               .append(" = ")
                               .append(el.getValue())
                               .append("\n"));
      LOGGER.info(sb.toString());
    }
    return failed;
  }

  private void processConfigElement(RequestConfigElement element) throws RequestConfigKeyParsingException {
    switch (element.getProperty()) {
      case "timeoutMs": _timeoutMs.add(element); break;
      case "batchingEnabled": _batchingEnabled.add(element); break;
      case "maxBatchSize": _maxBatchSize.add(element); break;
      default: throw new RequestConfigKeyParsingException("Unrecognized property: " + element.getProperty());
    }
  }

  @Override
  public RequestConfig apply(Request<?> request) {
    RequestConfigCacheKey cacheKey = new RequestConfigCacheKey(_inboundRequestContextFinder.find(), request);
    RequestConfig config = _cache.computeIfAbsent(cacheKey, this::resolve);
    if (_cache.size() > CONFIG_CACHE_SIZE) {
      //we might need a better strategy if cache fills up frequently
      //the expectation is that it will fill up very rarely
      _cache.clear();
    }
    return config;
  }

  private RequestConfig resolve(RequestConfigCacheKey cacheKey) {
    return new RequestConfigBuilder()
      .setTimeoutMs(_timeoutMs.resolve(cacheKey))
      .setBatchingEnabled(_batchingEnabled.resolve(cacheKey))
      .setMaxBatchSize(_maxBatchSize.resolve(cacheKey))
      .build();
  }

  /**
   * Default configuration map must specify default values for all properties.
   */
  private static ParSeqRestliClientConfig createDefaultConfig() {
    ParSeqRestliClientConfigBuilder builder = new ParSeqRestliClientConfigBuilder();
    builder.addTimeoutMs("*.*/*.*", DEFAULT_TIMEOUT);
    builder.addBatchingEnabled("*.*/*.*", DEFAULT_BATCHING_ENABLED);
    builder.addMaxBatchSize("*.*/*.*", DEFAULT_MAX_BATCH_SIZE);
    return builder.build();
  }
}


package com.linkedin.parseq.retry.backoff;

import java.util.function.Function;

/**
 * A policy that delegates to another policy that is selected based on the most recent error.
 *
 * @author Oleg Anashkin (oleg.anashkin@gmail.com)
 */
public class SelectedBackoff implements BackoffPolicy {
  protected final Function<Throwable, BackoffPolicy> _policyFunction;

  /**
   * A policy that delegates to another policy that is selected based on the most recent error.
   *
   * @param policyFunction The function that maps from errors to backoff policies.
   */
  public SelectedBackoff(Function<Throwable, BackoffPolicy> policyFunction) {
    _policyFunction = policyFunction;
  }

  /**
   * {@inheritDoc}
   */
  @Override
  public long nextBackoff(int attempts, Throwable error) {
    return _policyFunction.apply(error).nextBackoff(attempts, error);
  }
}


/*
 * Copyright 2012 LinkedIn, Inc
 *
 * 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.linkedin.parseq.internal;

import com.linkedin.parseq.promise.CountDownPromiseListener;
import com.linkedin.parseq.promise.FastFailCountDownPromiseListener;
import com.linkedin.parseq.promise.Promise;
import com.linkedin.parseq.promise.PromiseListener;
import com.linkedin.parseq.promise.Promises;
import com.linkedin.parseq.promise.SettablePromise;
import com.linkedin.parseq.Task;

/**
 * @author Chi Chan (ckchan@linkedin.com)
 */
public class InternalUtil {
  private InternalUtil() {
  }

  /**
   * Invokes the {@code listener} once all given {@code promises} have been resolved.
   *
   * @param listener the listener to invoke when the promises have finished.
   * @param promises the set of promises that must be resolved
   *
   * @see com.linkedin.parseq.promise.PromiseListener
   */
  public static void after(final PromiseListener<?> listener, final Promise<?>... promises) {
    final SettablePromise<Object> afterPromise = Promises.settable();
    final PromiseListener<Object> countDownListener =
        new CountDownPromiseListener<Object>(promises.length, afterPromise, null);

    for (Promise<?> promise : promises) {
      unwildcardPromise(promise).addListener(countDownListener);
    }

    afterPromise.addListener(unwildcardListener(listener));
  }

  /**
   * Invokes the {@code listener} once all given {@code promises} have been resolved
   * or any of promises has been resolved with failure.
   *
   * @param listener the listener to invoke when the promises have finished.
   * @param promises the set of promises that must be resolved
   *
   * @see com.linkedin.parseq.promise.PromiseListener
   */
  public static void fastFailAfter(final PromiseListener<?> listener, final Promise<?>... promises) {
    final SettablePromise<Object> afterPromise = Promises.settable();
    final PromiseListener<Object> countDownListener =
        new FastFailCountDownPromiseListener<Object>(promises.length, afterPromise, null);

    for (Promise<?> promise : promises) {
      unwildcardPromise(promise).addListener(countDownListener);
    }

    afterPromise.addListener(unwildcardListener(listener));
  }

  @SuppressWarnings("unchecked")
  public static Task<Object> unwildcardTask(Task<?> task) {
    return (Task<Object>) task;
  }

  @SuppressWarnings("unchecked")
  public static Promise<Object> unwildcardPromise(Promise<?> promise) {
    return (Promise<Object>) promise;
  }

  @SuppressWarnings("unchecked")
  public static PromiseListener<Object> unwildcardListener(PromiseListener<?> listener) {
    return (PromiseListener<Object>) listener;
  }
}


package com.linkedin.parseq;

import java.util.Collection;
import java.util.concurrent.TimeUnit;
import java.util.function.Function;

import com.linkedin.parseq.function.Tuple5;
import com.linkedin.parseq.promise.PromiseException;
import com.linkedin.parseq.promise.PromiseListener;
import com.linkedin.parseq.promise.PromiseUnresolvedException;
import com.linkedin.parseq.trace.ShallowTrace;
import com.linkedin.parseq.trace.ShallowTraceBuilder;
import com.linkedin.parseq.trace.Trace;
import com.linkedin.parseq.trace.TraceBuilder;

public class Tuple5TaskDelegate<T1, T2, T3, T4, T5> implements Tuple5Task<T1, T2, T3, T4, T5> {

  private final Task<Tuple5<T1, T2, T3, T4, T5>> _task;

  public Tuple5TaskDelegate(Task<Tuple5<T1, T2, T3, T4, T5>> task) {
    _task = task;
  }

  /**
   * {@inheritDoc}
   */
  @Override
  public boolean cancel(Exception reason) {
    return _task.cancel(reason);
  }

  /**
   * {@inheritDoc}
   */
  @Override
  public Tuple5<T1, T2, T3, T4, T5> get() throws PromiseException {
    return _task.get();
  }

  /**
   * {@inheritDoc}
   */
  @Override
  public Throwable getError() throws PromiseUnresolvedException {
    return _task.getError();
  }

  /**
   * {@inheritDoc}
   */
  @Override
  public Tuple5<T1, T2, T3, T4, T5> getOrDefault(Tuple5<T1, T2, T3, T4, T5> defaultValue) throws PromiseUnresolvedException {
    return _task.getOrDefault(defaultValue);
  }

  /**
   * {@inheritDoc}
   */
  @Override
  public String getName() {
    return _task.getName();
  }

  /**
   * {@inheritDoc}
   */
  @Override
  public int getPriority() {
    return _task.getPriority();
  }

  /**
   * {@inheritDoc}
   */
  @Override
  public boolean setPriority(int priority) {
    return _task.setPriority(priority);
  }

  /**
   * {@inheritDoc}
   */
  @Override
  public void await() throws InterruptedException {
    _task.await();
  }

  /**
   * {@inheritDoc}
   */
  @Override
  public void contextRun(Context context, Task<?> parent, Collection<Task<?>> predecessors) {
    _task.contextRun(context, parent, predecessors);
  }

  /**
   * {@inheritDoc}
   */
  @Override
  public boolean await(long time, TimeUnit unit) throws InterruptedException {
    return _task.await(time, unit);
  }

  /**
   * {@inheritDoc}
   */
  @Override
  public ShallowTrace getShallowTrace() {
    return _task.getShallowTrace();
  }

  /**
   * {@inheritDoc}
   */
  @Override
  public void addListener(PromiseListener<Tuple5<T1, T2, T3, T4, T5>> listener) {
    _task.addListener(listener);
  }

  /**
   * {@inheritDoc}
   */
  @Override
  public Trace getTrace() {
    return _task.getTrace();
  }

  /**
   * {@inheritDoc}
   */
  @Override
  public boolean isDone() {
    return _task.isDone();
  }

  /**
   * {@inheritDoc}
   */
  @Override
  public boolean isFailed() {
    return _task.isFailed();
  }

  /**
   * {@inheritDoc}
   */
  @Override
  public Long getId() {
    return _task.getId();
  }

  @Override
  public void setTraceValueSerializer(Function<Tuple5<T1, T2, T3, T4, T5>, String> serializer) {
    _task.setTraceValueSerializer(serializer);
  }

  @Override
  public ShallowTraceBuilder getShallowTraceBuilder() {
    return _task.getShallowTraceBuilder();
  }

  @Override
  public TraceBuilder getTraceBuilder() {
    return _task.getTraceBuilder();
  }

}


package com.linkedin.parseq;

import java.util.Collection;
import java.util.concurrent.TimeUnit;
import java.util.function.Function;

import com.linkedin.parseq.function.Tuple3;
import com.linkedin.parseq.promise.PromiseException;
import com.linkedin.parseq.promise.PromiseListener;
import com.linkedin.parseq.promise.PromiseUnresolvedException;
import com.linkedin.parseq.trace.ShallowTrace;
import com.linkedin.parseq.trace.ShallowTraceBuilder;
import com.linkedin.parseq.trace.Trace;
import com.linkedin.parseq.trace.TraceBuilder;

public class Tuple3TaskDelegate<T1, T2, T3> implements Tuple3Task<T1, T2, T3> {

  private final Task<Tuple3<T1, T2, T3>> _task;

  public Tuple3TaskDelegate(Task<Tuple3<T1, T2, T3>> task) {
    _task = task;
  }

  /**
   * {@inheritDoc}
   */
  @Override
  public boolean cancel(Exception reason) {
    return _task.cancel(reason);
  }

  /**
   * {@inheritDoc}
   */
  @Override
  public Tuple3<T1, T2, T3> get() throws PromiseException {
    return _task.get();
  }

  /**
   * {@inheritDoc}
   */
  @Override
  public Throwable getError() throws PromiseUnresolvedException {
    return _task.getError();
  }

  /**
   * {@inheritDoc}
   */
  @Override
  public Tuple3<T1, T2, T3> getOrDefault(Tuple3<T1, T2, T3> defaultValue) throws PromiseUnresolvedException {
    return _task.getOrDefault(defaultValue);
  }

  /**
   * {@inheritDoc}
   */
  @Override
  public String getName() {
    return _task.getName();
  }

  /**
   * {@inheritDoc}
   */
  @Override
  public int getPriority() {
    return _task.getPriority();
  }

  /**
   * {@inheritDoc}
   */
  @Override
  public boolean setPriority(int priority) {
    return _task.setPriority(priority);
  }

  /**
   * {@inheritDoc}
   */
  @Override
  public void await() throws InterruptedException {
    _task.await();
  }

  /**
   * {@inheritDoc}
   */
  @Override
  public void contextRun(Context context, Task<?> parent, Collection<Task<?>> predecessors) {
    _task.contextRun(context, parent, predecessors);
  }

  /**
   * {@inheritDoc}
   */
  @Override
  public boolean await(long time, TimeUnit unit) throws InterruptedException {
    return _task.await(time, unit);
  }

  /**
   * {@inheritDoc}
   */
  @Override
  public ShallowTrace getShallowTrace() {
    return _task.getShallowTrace();
  }

  /**
   * {@inheritDoc}
   */
  @Override
  public void addListener(PromiseListener<Tuple3<T1, T2, T3>> listener) {
    _task.addListener(listener);
  }

  /**
   * {@inheritDoc}
   */
  @Override
  public Trace getTrace() {
    return _task.getTrace();
  }

  /**
   * {@inheritDoc}
   */
  @Override
  public boolean isDone() {
    return _task.isDone();
  }

  /**
   * {@inheritDoc}
   */
  @Override
  public boolean isFailed() {
    return _task.isFailed();
  }

  /**
   * {@inheritDoc}
   */
  @Override
  public Long getId() {
    return _task.getId();
  }

  @Override
  public void setTraceValueSerializer(Function<Tuple3<T1, T2, T3>, String> serializer) {
    _task.setTraceValueSerializer(serializer);
  }

  @Override
  public ShallowTraceBuilder getShallowTraceBuilder() {
    return _task.getShallowTraceBuilder();
  }

  @Override
  public TraceBuilder getTraceBuilder() {
    return _task.getTraceBuilder();
  }

}


package com.linkedin.parseq.lambda;

import java.util.Arrays;
import java.util.Optional;

class SourcePointer {

  String _className;
  String _callingMethod;
  int _lineNumber;

  private SourcePointer(String className, String methodName, Integer lineNumber) {
    _className = className;
    _callingMethod = methodName;
    _lineNumber = lineNumber;
  }

  /* package private */ static Optional<SourcePointer> get() {
    //create an exception, discard known elements from stack trace and find first element with suspect
    return Arrays.stream(new Exception().getStackTrace())
        .filter(SourcePointer::notLambdaStuff)
        .findFirst()
        .map(SourcePointer::sourcePointer);
  }

  private static boolean notLambdaStuff(StackTraceElement element) {
    return !(element.getClassName().startsWith("java.")
        || element.getClassName().startsWith("sun.")
        || element.getClassName().startsWith("org.objectweb.asm.")
        || element.getClassName().startsWith(ASMBasedTaskDescriptor.class.getName())
        || element.getClassName().startsWith(ASMBasedTaskDescriptor.Agent.class.getName())
        || element.getClassName().startsWith(FindMethodCallAnalyzer.class.getName())
        || element.getClassName().startsWith(LambdaClassLocator.class.getName())
        || element.getClassName().startsWith(LambdaMethodVisitor.class.getName())
        || element.getClassName().startsWith(SourcePointer.class.getName())
        || element.getClassName().startsWith(SyntheticLambdaAnalyzer.class.getName())
        || element.getClassName().startsWith(SyntheticLambdaAnalyzer.SyntheticLambdaMethodVisitor.class.getName())
        || element.getMethodName().startsWith("lambda$")
        || element.getClassName().contains("$$Lambda$"));
  }

  private static SourcePointer sourcePointer(StackTraceElement stackTraceElement) {
    return new SourcePointer(stackTraceElement.getClassName(), stackTraceElement.getMethodName(),
        stackTraceElement.getLineNumber());
  }

  public void setLineNumber(int lineNumber) {
    _lineNumber = lineNumber;
  }

  public void setClassName(String className) {
    _className = className;
  }

  public void setCallingMethod(String callingMethod) {
    _callingMethod = callingMethod;
  }

  public String getClassName() {
    return _className;
  }

  @Override
  public String toString() {
    String classAndLine = Util.extractSimpleName(_className, ".") + (_lineNumber > 0 ? ":" + _lineNumber : "");
    if (_callingMethod != null) {
      return _callingMethod + "(" + classAndLine + ")";
    } else {
      return classAndLine;
    }
  }
}