/*
 * 2012-3 Red Hat Inc. and/or its affiliates and other contributors.
 *
 * 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 org.overlord.rtgov.elasticsearch.rest;

import org.apache.http.Header;
import org.apache.http.HttpResponse;
import org.apache.http.message.BasicHeader;
import org.apache.http.message.HeaderGroup;

import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.OutputStream;
import java.net.ConnectException;
import java.util.logging.Level;
import java.util.logging.Logger;

/**
 * This class implements the authenticating proxy for the ElasticSearch server.
 * 
 * Based on the http servlet proxy implemented by David Smiley:
 * https://github.com/dsmiley/HTTP-Proxy-Servlet
 */
public class ElasticsearchRESTServer extends HttpServlet {

    private static final long serialVersionUID = 1L;
    
    private static final Logger LOG=Logger.getLogger(ElasticsearchRESTServer.class.getName());

    private ElasticsearchHttpClient _client=new ElasticsearchHttpClient();

    /**
     * {@inheritDoc}
     */
    protected void service(HttpServletRequest servletRequest, HttpServletResponse servletResponse)
                        throws IOException {

        if (isSupported(servletRequest)) {
            try {
                HttpResponse resp=_client.process(servletRequest);
                
                servletResponse.setStatus(resp.getStatusLine().getStatusCode());
    
                copyResponseHeaders(resp, servletResponse);
    
                // Send the content to the client
                copyResponseContent(resp, servletResponse);
    
            } catch (ConnectException ce) {
                // Return "Service Unavailable" status code
                servletResponse.setStatus(503);
                
            } catch (IOException ioe) {
                throw ioe;
            } catch (Exception e) {
                throw new IOException("Failed to process gateway request", e);
            }
        } else {
            if (LOG.isLoggable(Level.FINER)) {
                LOG.finer("Forbidden request: method="+servletRequest.getMethod()+" path="+servletRequest.getPathInfo());
            }
            
            // Return "Forbidden" status code
            servletResponse.setStatus(403);
        }
    }
    
    /**
     * This method determines whether the supplied request is supported by this gateway.
     * 
     * @param servletRequest The REST request
     * @return Whether the request is supported
     */
    protected boolean isSupported(HttpServletRequest servletRequest) {
        if (servletRequest.getMethod().equalsIgnoreCase("get")) {
            return (true);
        } else if (servletRequest.getMethod().equalsIgnoreCase("post")) {
            if (servletRequest.getPathInfo().endsWith("/_search")) {
                return (true);
            }
        } else if (servletRequest.getMethod().equalsIgnoreCase("put")) {
            if (servletRequest.getPathInfo().startsWith("/kibana-int/dashboard")) {
                return (true);
            }
        }
        return (false);
    }

    /** 
     * Copy proxied response headers back to the servlet client. 
     * 
     * @param proxyResponse The response from the target server
     * @param servletResponse The response back to the client
     */
    protected void copyResponseHeaders(HttpResponse proxyResponse, HttpServletResponse servletResponse) {

        for (int i=0; i < proxyResponse.getAllHeaders().length; i++) {
            Header header=proxyResponse.getAllHeaders()[i];

            if (HOPBYHOPHEADERS.containsHeader(header.getName())) {
                continue;
            }

            servletResponse.addHeader(header.getName(), header.getValue());
        }
    }

    /** 
     * Copy response body data (the entity) from the proxy to the servlet client.
     * 
     * @param proxyResponse The response from the target server
     * @param servletResponse The response back to the client
     * @throws IOException Failed to copy content
     */
    protected void copyResponseContent(HttpResponse proxyResponse, HttpServletResponse servletResponse) throws IOException {
        OutputStream servletOutputStream = servletResponse.getOutputStream();

        try {
            proxyResponse.getEntity().writeTo(servletOutputStream);

        } finally {
            try {
                servletOutputStream.close();
            } catch (Exception e) {
                log(e.getMessage(),e);
            }
        }
    }

    /** These are the "hop-by-hop" headers that should not be copied.
     * http://www.w3.org/Protocols/rfc2616/rfc2616-sec13.html
     * I use an HttpClient HeaderGroup class instead of Set<String> because this
     * approach does case insensitive lookup faster.
     */
    private static final HeaderGroup HOPBYHOPHEADERS;
    
    static {
        HOPBYHOPHEADERS = new HeaderGroup();
        String[] headers = new String[] {
                "Connection", "Keep-Alive", "Proxy-Authenticate", "Proxy-Authorization",
                "TE", "Trailers", "Transfer-Encoding", "Upgrade" };
        for (String header : headers) {
            HOPBYHOPHEADERS.addHeader(new BasicHeader(header, null));
        }
    }

}

@@ -977,27 +977,81 @@
 java.io.
-IOException
+Closeable;%0Aimport java.io.IOException;%0Aimport java.io.InputStream
 ;%0Aimport
@@ -4501,32 +4501,183 @@
 client.%0A     * %0A
+     * Ensures that proxyInputStream will be closed in any case by closing the inputStream%0A     * @see org.apache.http.HttpEntity.getContent()%0A     * %0A
      * @param pr
@@ -4977,28 +4977,199 @@
         
-OutputStream
+InputStream proxyInputStream = null;%0A        OutputStream servletOutputStream = null;%0A%0A        try %7B%0A            proxyInputStream = proxyResponse.getEntity().getContent();%0A           
  servlet
@@ -5218,25 +5218,24 @@
 ream();%0A
-%0A
         
 try %7B%0A  
@@ -5218,37 +5218,36 @@
 ream();%0A        
-try %7B
+    
 %0A            pro
@@ -5299,17 +5299,16 @@
 tream);%0A
-%0A
         
@@ -5327,35 +5327,212 @@
             
+secureCloseStream(proxyInputStream);%0A            secureCloseStream(servletOutputStream);%0A        %7D%0A    %7D%0A%0A    private void secureCloseStream(Closeable servletOutputStream) %7B%0A        
 try %7B%0A
-     
            s
@@ -5522,32 +5522,33 @@
 ry %7B%0A           
+ 
 servletOutputStr
@@ -5560,36 +5560,32 @@
 lose();%0A        
-    
 %7D catch (Excepti
@@ -5604,20 +5604,16 @@
         
- 
-   
 log(e.ge
@@ -5623,38 +5623,24 @@
 ssage(),e);%0A
-            %7D%0A
         %7D%0A  

Fix for RTGOV-674: ElasticsearchRESTServer proxy blocks on Error