Package com.google.dataconnector.client

Source Code of com.google.dataconnector.client.FetchRequestHandler

/* Copyright 2010 Google 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.
*
* $Id: FetchRequestHandler.java 552 2010-10-11 21:33:41Z dchung@google.com $
*/
package com.google.dataconnector.client;

import java.io.CharArrayWriter;
import java.io.PrintWriter;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.concurrent.Callable;
import java.util.concurrent.ThreadPoolExecutor;

import org.apache.log4j.Logger;

import com.google.common.base.Preconditions;
import com.google.dataconnector.client.fetchrequest.HttpFetchStrategy;
import com.google.dataconnector.client.fetchrequest.URLConnectionStrategy;
import com.google.dataconnector.protocol.Dispatchable;
import com.google.dataconnector.protocol.FrameSender;
import com.google.dataconnector.protocol.FramingException;
import com.google.dataconnector.protocol.proto.SdcFrame.FetchReply;
import com.google.dataconnector.protocol.proto.SdcFrame.FetchRequest;
import com.google.dataconnector.protocol.proto.SdcFrame.FrameInfo;
import com.google.dataconnector.protocol.proto.SdcFrame.MessageHeader;
import com.google.dataconnector.util.AgentConfigurationException;
import com.google.dataconnector.util.ClockUtil;
import com.google.dataconnector.util.SdcKeysManager;
import com.google.dataconnector.util.SessionEncryption;
import com.google.inject.Inject;
import com.google.inject.Injector;
import com.google.protobuf.ByteString;
import com.google.protobuf.InvalidProtocolBufferException;

/**
* Handler for the {@link FetchRequest} FrameInfo type.
* @author dchung@google.com (David Chung)
*
*/
public class FetchRequestHandler implements Dispatchable {

  /**
   * Special header in the FetchRequest that is understood by the agent.
   */
  public static final String DEBUG_HEADER =
    "x-sdc-agent-request-report-exception-stacktrace";

  /**
   * Status code from processing the agent request.
   */
  public enum StatusCode {
    OK(200),
    BAD_REQUEST(501),
    IO_EXCEPTION(502),
    STRATEGY_EXCEPTION(503),
    AGENT_ERROR(504);

    private int value;
    private StatusCode(int c) {
      value = c;
    }
  }

  /**
   * Interface for handling specific type of FetchRequest.
   */
  public interface Strategy {

    /**
     * Given the request, fill in the results in the reply provided.
     *
     * @param request The request.
     * @param replyBuilder The reply to fill in.
     * @throws StrategyException Any exception during processing.
     */
    public void process(FetchRequest request, FetchReply.Builder replyBuilder)
    throws StrategyException;
  }

  private static Logger LOG = Logger.getLogger(FetchRequestHandler.class);

  // Injected Dependencies.
  private final SdcKeysManager sdcKeysManager;
  private final ThreadPoolExecutor threadPoolExecutor;
  private final Injector injector;
  private final ClockUtil clock;

  // Runtime Dependencies.
  private FrameSender frameSender;

  /**
   * Constructor with dependency on thread pool for asynchronous fetch and
   * sending of replies.
   *
   * @param km The session key manager.
   * @param threadPoolExecutor The thread pool.
   * @param injector The injector.
   */
  @Inject
  public FetchRequestHandler(SdcKeysManager km, ThreadPoolExecutor threadPoolExecutor,
      Injector injector, ClockUtil clock) {
    this.sdcKeysManager = km;
    this.threadPoolExecutor = threadPoolExecutor;
    this.injector = injector;
    this.clock = clock;
  }

  public final void setFrameSender(FrameSender frameSender) {
    this.frameSender = frameSender;
  }

  /**
   * Handles the request coming from the cloud.  In this case, fetch data from
   * the requested resource in the {@link FetchRequest}.
   *
   * @param frameInfo The container data frame.
   */
  @Override
  public void dispatch(FrameInfo frameInfo) throws FramingException {
    // Session encryption: decrypt the message from the cloud:
    if (!this.sdcKeysManager.hasSessionEncryption()) {
      LOG.warn("Cannot decrypt message for fetch protocol: no session encryption.");
      return;
    }

    FetchRequest request;
    try {
      request = sdcKeysManager.getSessionEncryption().getFrom(frameInfo,
          new SessionEncryption.Parse<FetchRequest>() {
        public FetchRequest parse(ByteString s) throws InvalidProtocolBufferException {
          return FetchRequest.parseFrom(s);
        }
      });
    } catch (InvalidProtocolBufferException e) {
      throw new FramingException(e);
    }

    if (request == null) {
      return;
    }
    // Now we have the request.  Check the request:
    FetchReply.Builder replyBuilder = FetchReply.newBuilder().setId(request.getId());
    try {
      validate(request);
    } catch (IllegalArgumentException e) {
      logExceptionInReply(request, replyBuilder, e);
      sendReply(replyBuilder.setStatus(StatusCode.BAD_REQUEST.value).build());
      LOG.warn(request.getId() + ": Bad request: " + request, e);
      throw new FramingException(e);
    } catch (MalformedURLException e) {
      logExceptionInReply(request, replyBuilder, e);
      sendReply(replyBuilder.setStatus(StatusCode.BAD_REQUEST.value).build());
      LOG.warn("Bad request: " + request, e);
      throw new FramingException(e);
    }

    // Now execute work asynchronously.
    try {
      StrategyType strategyType = StrategyType.match(request.getStrategy());
      Strategy strategy = injector.getInstance(strategyType.strategyClz);
      ResourceFetcher fetcher = new ResourceFetcher(request, strategy);
      threadPoolExecutor.submit(fetcher);
    } catch (Exception e) {
      LOG.warn(request.getId() + ": Agent error: " + request, e);
      throw new FramingException(e);
    }
  }
 
  /**
   * Simple enum defined to map the FetchRequest's scheme field to an enum
   * and a strategy class.
   */
  public enum StrategyType {

    URL_CONNECTION("URLConnection", URLConnectionStrategy.class),

    HTTP_CLIENT("HttpClient", HttpFetchStrategy.class);

    private String scheme;
    private Class<? extends Strategy> strategyClz;

    private StrategyType(String scheme, Class<? extends Strategy> clz) {
      this.scheme = scheme;
      this.strategyClz = clz;
    }

    /**
     * Given the scheme expression, find the strategy that matches it.
     * @param scheme The scheme from the FetchRequest.
     * @return The strategy type.
     */
    public static StrategyType match(String scheme) {
      if (scheme != null) {
        for (StrategyType st : StrategyType.values()) {
          if (st.scheme.matches(scheme)) {
            return st;
          }
        }
      }
      // Default is HttpClient
      return HTTP_CLIENT;
    }
  }

  /**
   * Class that performs the actual fetching of the resource.
   */
  class ResourceFetcher implements Callable<FetchReply> {

    private final FetchRequest request;
    private final Strategy strategy;
    private FetchReply reply;

    /**
     * Constructs an instance to fetch the specified resource URL.
     */
    ResourceFetcher(FetchRequest request, Strategy strategy) {
      this.request = request;
      this.strategy = strategy;
    }

    /**
     * Fetch the resource specified at creation of the fetcher.
     */
    @Override
    public FetchReply call() {
      // Initialize the reply, etc.
      StatusCode statusCode = StatusCode.OK;
      FetchReply.Builder replyBuilder = FetchReply.newBuilder();
      FetchReply reply = null;

      replyBuilder.setId(request.getId());

      Exception exception = null;
      try {

        long start = clock.currentTimeMillis();
        strategy.process(request, replyBuilder);
        replyBuilder.setLatency(clock.currentTimeMillis() - start);

        if (!replyBuilder.hasStatus()) {
          replyBuilder.setStatus(statusCode.value);
        }

        reply = replyBuilder.build();
        sendReply(reply);
        return reply;

      } catch (StrategyException e) {
        exception = e;
        replyBuilder.setStatus(StatusCode.STRATEGY_EXCEPTION.value);
        logExceptionInReply(request, replyBuilder, e);
        sendReply(reply);
      } catch (Exception e) {
        exception = e;
        // Do not send reply.
        replyBuilder.setStatus(StatusCode.AGENT_ERROR.value).build();
      }
      LOG.warn(request.getId() + ": Exception while fetching " + request, exception);
      return replyBuilder.build();
    }

    @Override
    public String toString() {
      return String.format("ResourceFetcher(request=%s,reply=%s)",
          this.request, this.reply);
    }
  }

  /**
   * If the request contains a special header for logging exception, send the
   * stacktrace back as a reply header.
   *
   * @param request The request.
   * @param replyBuilder Reply builder.
   * @param ex The exception to log.
   */
  void logExceptionInReply(FetchRequest request,
      FetchReply.Builder replyBuilder, Exception ex) {
    if (containsDebugHeader(request)) {
      CharArrayWriter cw = new CharArrayWriter();
      ex.printStackTrace(new PrintWriter(cw));
      cw.flush();
      // add a debug stack trace to reply header
      replyBuilder.addHeaders(MessageHeader.newBuilder()
          .setKey(DEBUG_HEADER)
          .setValue(cw.toString()).build());
    }
  }

  /**
   * Asynchronously sends the reply to the cloud.
   * @param reply The reply.
   */
  void sendReply(FetchReply reply) {
    Preconditions.checkNotNull(frameSender);
    // Encrypt the reply.
    // Session encryption: decrypt the message from the cloud:
    if (!this.sdcKeysManager.hasSessionEncryption()) {
      LOG.warn("Cannot encrypt message for fetch protocol: no session encryption. Not sent.");
      return;
    }
    LOG.info(reply.getId() + ": Sending reply status=" + reply.getStatus() +
        ", latency=" + reply.getLatency());
    LOG.debug("Sending reply =" + reply);

    FrameInfo frame = this.sdcKeysManager.getSessionEncryption().toFrameInfo(
        FrameInfo.Type.FETCH_REQUEST, reply);

    if (frame != null) {
      frameSender.sendFrame(frame);
    }
  }

  /**
   * Validates the incoming request.
   * @param request The request.
   * @throws IllegalArgumentException
   * @throws MalformedURLException
   */
  void validate(FetchRequest request)
  throws IllegalArgumentException, MalformedURLException {
    // Sanity check for request id and resource.
    Preconditions.checkArgument(request.hasId() && request.getId().length() > 0);
    Preconditions.checkArgument(request.hasResource());
    Preconditions.checkArgument(request.getResource().length() > 0);
    // Now get the resource as URL:
    new URL(request.getResource());
  }

  /**
   * Returns true if a debug header is in the request.
   * @param request The request.
   * @return True if request has debug header.
   */
  boolean containsDebugHeader(FetchRequest request) {
    return headerMatchesValue(request, DEBUG_HEADER, "true");
  }

  /**
   * Given the header key, returns true if the value exists and matches the
   * string provided.
   *
   * @param key The key.
   * @param matchExp The value/ expression to match.
   * @return True if header exists AND matches the expression, case insensitive.
   */
  boolean headerMatchesValue(FetchRequest request, String key, String matchExp) {
    for (MessageHeader h : request.getHeadersList()) {
      if (key.equals(h.getKey()) &&
          h.getValue().toLowerCase().matches(matchExp.toLowerCase())) {
        return true;
      }
    }
    return false;
  }
}
TOP

Related Classes of com.google.dataconnector.client.FetchRequestHandler

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.