/*
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you 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 org.apache.shindig.gadgets.render;

import org.apache.shindig.common.JsonSerializer;
import org.apache.shindig.common.uri.Uri;
import org.apache.shindig.common.uri.UriBuilder;
import org.apache.shindig.common.util.CharsetUtil;
import org.apache.shindig.gadgets.Gadget;
import org.apache.shindig.gadgets.GadgetContext;
import org.apache.shindig.gadgets.GadgetException;
import org.apache.shindig.gadgets.http.HttpCache;
import org.apache.shindig.gadgets.http.HttpRequest;
import org.apache.shindig.gadgets.http.HttpResponse;
import org.apache.shindig.gadgets.http.RequestPipeline;
import org.apache.shindig.gadgets.oauth.OAuthArguments;
import org.apache.shindig.gadgets.preload.PipelineExecutor;
import org.apache.shindig.gadgets.spec.PipelinedData;
import org.apache.shindig.gadgets.spec.View;

import com.google.common.base.Preconditions;
import com.google.common.collect.ImmutableList;
import com.google.inject.Inject;

import javax.servlet.http.HttpServletResponse;

/**
 * Implements proxied rendering.
 */
public class ProxyRenderer {
  public static final String PATH_PARAM = "path";
  public static final String UA_IDENT = "Shindig";

  private final RequestPipeline requestPipeline;
  private final HttpCache httpCache;
  private final PipelineExecutor pipelineExecutor;

  /**
   * @param requestPipeline Used for performing the proxy request. Always ignores caching because
   *                        we want to skip preloading when the object is in the cache.
   * @param httpCache The shared http cache. Used before checking the request pipeline to determine
   *                  whether to perform the preload / fetch cycle.
   */
  @Inject
  public ProxyRenderer(RequestPipeline requestPipeline,
      HttpCache httpCache, PipelineExecutor pipelineExecutor) {
    this.requestPipeline = requestPipeline;
    this.httpCache = httpCache;
    this.pipelineExecutor = pipelineExecutor;
  }

  public String render(Gadget gadget) throws RenderingException, GadgetException {
    View view = gadget.getCurrentView();
    Uri href = view.getHref();
    Preconditions.checkArgument(href != null, "Gadget does not have href for the current view");

    GadgetContext context = gadget.getContext();
    String path = context.getParameter(PATH_PARAM);
    if (path != null) {
      try {
        Uri relative = Uri.parse(path);
        if (!relative.isAbsolute()) {
          href = href.resolve(relative);
        }
      } catch (IllegalArgumentException e) {
        // TODO: Spec does not say what to do for an invalid relative path.
        // Just ignoring for now.
      }
    }

    UriBuilder uri = new UriBuilder(href);
    uri.addQueryParameter("lang", context.getLocale().getLanguage());
    uri.addQueryParameter("country", context.getLocale().getCountry());

    OAuthArguments oauthArgs = new OAuthArguments(view);
    oauthArgs.setProxiedContentRequest(true);

    HttpRequest request = new HttpRequest(uri.toUri())
        .setIgnoreCache(context.getIgnoreCache())
        .setOAuthArguments(oauthArgs)
        .setAuthType(view.getAuthType())
        .setSecurityToken(context.getToken())
        .setContainer(context.getContainer())
        .setGadget(gadget.getSpec().getUrl());
    setUserAgent(request, context);

    HttpResponse response = httpCache.getResponse(request);

    if (response == null || response.isStale()) {
      HttpRequest proxyRequest = createPipelinedProxyRequest(gadget, request);
      response = requestPipeline.execute(proxyRequest);
      httpCache.addResponse(request, response);
    }

    if (response.isError()) {
      throw new RenderingException("Unable to reach remote host. HTTP status " +
        response.getHttpStatusCode(), HttpServletResponse.SC_NOT_FOUND);
    }

    return response.getResponseAsString();
  }

  /**
   * Creates a proxy request by fetching pipelined data and adding it to an existing request.
   *
   */
  private HttpRequest createPipelinedProxyRequest(Gadget gadget, HttpRequest original) {
    HttpRequest request = new HttpRequest(original);
    request.setIgnoreCache(true);

    PipelinedData data = gadget.getCurrentView().getPipelinedData();
    if (data != null) {
      PipelineExecutor.Results results =
        pipelineExecutor.execute(gadget.getContext(), ImmutableList.of(data));

      if (results != null && !results.results.isEmpty()) {
        String postContent = JsonSerializer.serialize(results.results);
        // POST the preloaded content, with a method override of GET
        // to enable caching
        request.setMethod("POST")
            .setPostBody(CharsetUtil.getUtf8Bytes(postContent))
            .setHeader("Content-Type", "application/json;charset=utf-8");
      }
    }

    return request;
  }

  /**
   * Sets the User-Agent header in the new request to a variant of the original
   * request's User-Agent, plus a small ident string for the gadget server.
   */
  private void setUserAgent(HttpRequest request, GadgetContext context) {
    String userAgent = context.getUserAgent();
    if (userAgent != null) {
      String myIdent = getUAIdent();
      if (myIdent != null) {
        userAgent = userAgent + ' ' + myIdent;
      }
      request.setHeader("User-Agent", userAgent);
    }
  }
  
  /**
   * Returns the program name which will be added at the end of the User-Agent
   * string, to identify the gadget server.
   */
  protected String getUAIdent() {
    return UA_IDENT;
  }
}

@@ -4707,21 +4707,23 @@
  */%0A  pr
-ivate
+otected
  HttpReq

Patch by Jan Luehe | http://codereview.appspot.com/1664050/ Change scope of ProxyRenderer#createPipelinedProxyRequest to protected (from private)