uk.co.tfd.sm.proxy.ProxyServlet.java Source code

Java tutorial

Introduction

Here is the source code for uk.co.tfd.sm.proxy.ProxyServlet.java

Source

/*
 * Licensed to the Sakai Foundation (SF) under one
 * or more contributor license agreements. See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership. The SF 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 uk.co.tfd.sm.proxy;

import java.io.File;
import java.io.FileReader;
import java.io.IOException;
import java.io.InputStream;
import java.util.Enumeration;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Properties;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;

import javax.servlet.Servlet;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.apache.commons.collections.EnumerationUtils;
import org.apache.felix.scr.annotations.Activate;
import org.apache.felix.scr.annotations.Component;
import org.apache.felix.scr.annotations.Property;
import org.apache.felix.scr.annotations.Reference;
import org.apache.felix.scr.annotations.ReferenceCardinality;
import org.apache.felix.scr.annotations.ReferencePolicy;
import org.apache.felix.scr.annotations.Service;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import uk.co.tfd.sm.api.proxy.ProxyClientException;
import uk.co.tfd.sm.api.proxy.ProxyClientService;
import uk.co.tfd.sm.api.proxy.ProxyPostProcessor;
import uk.co.tfd.sm.api.proxy.ProxyPreProcessor;
import uk.co.tfd.sm.api.proxy.ProxyResponse;

import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableMap.Builder;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Maps;

/**
 * This servlet binds to a resource that defines an end point.
 * 
 */
@Component(immediate = true, metatype = true)
@Service(value = Servlet.class)
@org.apache.felix.scr.annotations.Properties(value = { @Property(name = "alias", value = "/proxy") })
public class ProxyServlet extends HttpServlet {

    private static final boolean DEFAULT_CACHE_CONFIG = false;

    @Property(boolValue = DEFAULT_CACHE_CONFIG)
    private static final String CACHE_CONFIG = "cache-config";

    protected static final String DEFAULT_TEMPLATE_PATH = "proxy/config";

    @Property(value = DEFAULT_TEMPLATE_PATH)
    protected static final String PROP_TEMPLATE_PATH = "templatePath";

    private static final String SAKAI_PROXY_REQUEST_BODY = "Sakai-Proxy-Request-Body";

    private static final String PUT_METHOD = "PUT";

    private static final String POST_METHOD = "POST";

    public static final String PROXY_PATH_PREFIX = "/var/proxy/";

    /**
    *
    */
    private static final String SAKAI_REQUEST_STREAM_BODY = "sakai:request-stream-body";

    /**
    *
    */
    private static final String BASIC_PASSWORD = ":basic-password";

    /**
    *
    */
    private static final String BASIC_USER = ":basic-user";

    /**
    *
    */
    private static final long serialVersionUID = -3190208378955330531L;

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

    @Reference
    transient ProxyClientService proxyClientService;

    private transient ProxyPostProcessor defaultPostProcessor = new DefaultProxyPostProcessorImpl();

    @Reference(policy = ReferencePolicy.DYNAMIC, cardinality = ReferenceCardinality.OPTIONAL_MULTIPLE, referenceInterface = ProxyPreProcessor.class, bind = "bindPreProcessor", unbind = "unbindPreProcessor")
    Map<String, ProxyPreProcessor> preProcessors = new ConcurrentHashMap<String, ProxyPreProcessor>();

    @Reference(policy = ReferencePolicy.DYNAMIC, cardinality = ReferenceCardinality.OPTIONAL_MULTIPLE, referenceInterface = ProxyPostProcessor.class, bind = "bindPostProcessor", unbind = "unbindPostProcessor")
    Map<String, ProxyPostProcessor> postProcessors = new ConcurrentHashMap<String, ProxyPostProcessor>();

    private static final Set<String> HEADER_BLACKLIST = ImmutableSet.of("Host", "Content-Length", "Content-Type",
            "Authorization");

    private static final String CLASSPATH_PREFIX = "uk/co/tfd/sm/proxy";

    private String baseFile;

    /**
     * There will almost certainly never be enough valid proxy maps to need this
     * to be cleared.
     */
    private Map<String, Map<String, Object>> configCache = Maps.newConcurrentMap();

    private boolean cacheConfig;

    @Activate
    protected void activate(Map<String, Object> properties) {
        baseFile = toString(properties.get(PROP_TEMPLATE_PATH), DEFAULT_TEMPLATE_PATH);
        cacheConfig = toBoolean(properties.get(CACHE_CONFIG), DEFAULT_CACHE_CONFIG);
    }

    private String toString(Object configValue, String defaultValue) {
        if (configValue == null) {
            return defaultValue;
        }
        return String.valueOf(configValue);
    }

    private boolean toBoolean(Object configValue, boolean defaultValue) {
        if (configValue == null) {
            return defaultValue;
        }
        return Boolean.parseBoolean(String.valueOf(configValue));
    }

    @SuppressWarnings("unchecked")
    @Override
    protected void service(HttpServletRequest request, HttpServletResponse response)
            throws ServletException, IOException {
        try {
            boolean proxyStream = false;
            if (POST_METHOD.equals(request.getMethod()) || PUT_METHOD.equals(request.getMethod())) {
                String proxyRequest = request.getHeader(SAKAI_PROXY_REQUEST_BODY);
                proxyStream = Boolean.parseBoolean(proxyRequest);
            }

            String path = request.getPathInfo();
            Map<String, Object> config = getConfig(path);
            if (config == null) {
                response.sendError(404);
                return;
            }

            if (!proxyStream) {
                String streamBody = (String) config.get(SAKAI_REQUEST_STREAM_BODY);
                if (streamBody != null) {
                    proxyStream = Boolean.parseBoolean(streamBody);
                }
            }

            Builder<String, Object> headersBuilder = ImmutableMap.builder();
            Builder<String, Object> templateParamsBuilder = ImmutableMap.builder();

            for (Enumeration<?> enames = request.getHeaderNames(); enames.hasMoreElements();) {

                String name = (String) enames.nextElement();
                if (HEADER_BLACKLIST.contains(name)) {
                    continue;
                }
                if (name.equals(BASIC_USER)) {
                    templateParamsBuilder.put(BASIC_USER, request.getHeader(BASIC_USER));
                } else if (name.equals(BASIC_PASSWORD)) {
                    templateParamsBuilder.put(BASIC_USER, request.getHeader(BASIC_USER));
                } else if (!name.startsWith(":")) {
                    headersBuilder.put(name, toSimpleString(
                            (String[]) EnumerationUtils.toList(request.getHeaders(name)).toArray(new String[0])));
                }
            }

            Map<String, Object> headers = headersBuilder.build();

            // collect the parameters and store into a mutable map.
            Map<String, String[]> rpm = request.getParameterMap();
            for (Entry<String, String[]> e : rpm.entrySet()) {
                templateParamsBuilder.put(e.getKey(), toSimpleString(e.getValue()));
            }

            Map<String, Object> templateParams = templateParamsBuilder.build();

            // we might want to pre-process the headers
            if (config.containsKey(ProxyPreProcessor.CONFIG_PREPROCESSOR)) {
                String preprocessorName = (String) config.get(ProxyPreProcessor.CONFIG_PREPROCESSOR);
                ProxyPreProcessor preprocessor = preProcessors.get(preprocessorName);
                if (preprocessor != null) {
                    preprocessor.preProcessRequest(request, headers, templateParams);
                } else {
                    LOGGER.warn("Unable to find pre processor of name {} for node {} ", preprocessorName, path);
                }
            }
            ProxyPostProcessor postProcessor = defaultPostProcessor;
            // we might want to post-process the headers
            if (config.containsKey(ProxyPostProcessor.CONFIG_POSTPROCESSOR)) {
                String postProcessorName = (String) config.get(ProxyPostProcessor.CONFIG_POSTPROCESSOR);
                if (postProcessors.containsKey(postProcessorName))
                    postProcessor = postProcessors.get(postProcessorName);
                if (postProcessor == null) {
                    LOGGER.warn("Unable to find post processor of name {} for node {} ", postProcessorName, path);
                    postProcessor = defaultPostProcessor;
                }
            }
            if (postProcessor instanceof CachingProxyProcessor) {
                CachingProxyProcessor cachingPostProcessor = (CachingProxyProcessor) postProcessor;
                if (cachingPostProcessor.sendCached(config, templateParams, response)) {
                    return;
                }
            }

            ProxyResponse proxyResponse = proxyClientService.executeCall(config, headers, templateParams, null, -1,
                    null);
            try {
                postProcessor.process(config, templateParams, response, proxyResponse);
            } finally {
                proxyResponse.close();
            }
        } catch (IOException e) {
            throw e;
        } catch (ProxyClientException e) {
            response.sendError(500, e.getMessage());
        }
    }

    private Object toSimpleString(String[] a) {
        if (a == null) {
            return null;
        }
        if (a.length == 1) {
            return a[0];
        }
        return a;
    }

    private Map<String, Object> getConfig(String pathInfo) throws IOException {
        if (pathInfo == null) {
            return null;
        }

        if (cacheConfig && configCache.containsKey(pathInfo)) {
            return configCache.get(pathInfo);
        } else {
            Properties p = new Properties();
            InputStream in = this.getClass().getClassLoader().getResourceAsStream(CLASSPATH_PREFIX + pathInfo);
            if (in != null) {
                LOGGER.info("Loading Classpath {} ", CLASSPATH_PREFIX + pathInfo);
                p.load(in);
                in.close();
            } else {
                LOGGER.info("Not Loading Classpath {} ", CLASSPATH_PREFIX + pathInfo);
                File configFile = new File(baseFile, pathInfo);
                if (!configFile.exists() || !configFile.isFile() || !configFile.canRead()) {
                    LOGGER.info("Not Loading File {} ", configFile.getAbsoluteFile());
                    return null;
                }
                LOGGER.info("Loading File {} ", configFile.getAbsoluteFile());
                FileReader fr = new FileReader(configFile);
                p.load(fr);
                fr.close();
            }
            Builder<String, Object> b = ImmutableMap.builder();
            for (Entry<Object, Object> e : p.entrySet()) {
                String k = String.valueOf(e.getKey());
                b.put(k, e.getValue());
            }
            Map<String, Object> config = b.build();
            configCache.put(pathInfo, config);
            return config;
        }
    }

    protected void bindPreProcessor(ProxyPreProcessor proxyPreProcessor) {
        preProcessors.put(proxyPreProcessor.getName(), proxyPreProcessor);
    }

    protected void unbindPreProcessor(ProxyPreProcessor proxyPreProcessor) {
        preProcessors.remove(proxyPreProcessor.getName());
    }

    protected void bindPostProcessor(ProxyPostProcessor proxyPostProcessor) {
        postProcessors.put(proxyPostProcessor.getName(), proxyPostProcessor);
    }

    protected void unbindPostProcessor(ProxyPostProcessor proxyPostProcessor) {
        postProcessors.remove(proxyPostProcessor.getName());
    }

}