ru.histone.resourceloaders.DefaultResourceLoader.java Source code

Java tutorial

Introduction

Here is the source code for ru.histone.resourceloaders.DefaultResourceLoader.java

Source

/**
 *    Copyright 2013 MegaFon
 *
 *    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 ru.histone.resourceloaders;

import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.node.ArrayNode;
import org.apache.commons.codec.binary.Base64;
import org.apache.http.Header;
import org.apache.http.HttpRequest;
import org.apache.http.HttpResponse;
import org.apache.http.ProtocolException;
import org.apache.http.client.HttpClient;
import org.apache.http.client.methods.*;
import org.apache.http.conn.ClientConnectionManager;
import org.apache.http.entity.StringEntity;
import org.apache.http.impl.client.AbstractHttpClient;
import org.apache.http.impl.client.DefaultHttpClient;
import org.apache.http.impl.client.DefaultRedirectStrategy;
import org.apache.http.impl.conn.BasicClientConnectionManager;
import org.apache.http.impl.conn.SchemeRegistryFactory;
import org.apache.http.protocol.HttpContext;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import ru.histone.HistoneTokensHolder;
import ru.histone.evaluator.functions.node.object.ToQueryString;
import ru.histone.evaluator.nodes.Node;
import ru.histone.evaluator.nodes.NodeFactory;
import ru.histone.evaluator.nodes.ObjectHistoneNode;
import ru.histone.parser.OldParser;
import ru.histone.parser.ParserException;
import ru.histone.tokenizer.TokenizerFactory;
import ru.histone.utils.BOMInputStream;
import ru.histone.utils.IOUtils;
import ru.histone.utils.PathUtils;

import java.io.*;
import java.net.URI;
import java.util.*;

/**
 * Histone default resource loader<br/>
 * Supports file and http protocols
 */
public class DefaultResourceLoader implements ResourceLoader {
    private static final Logger log = LoggerFactory.getLogger(DefaultResourceLoader.class);

    private ClientConnectionManager httpClientConnectionManager = new BasicClientConnectionManager(
            SchemeRegistryFactory.createDefault());

    private TokenizerFactory tokenizerFactory = new TokenizerFactory(HistoneTokensHolder.getTokens());
    private NodeFactory nodeFactory = new NodeFactory(new ObjectMapper());
    private OldParser parser = new OldParser(tokenizerFactory, nodeFactory);

    @Override
    public String resolveFullPath(String location, String baseLocation) throws ResourceLoadException {
        log.debug("Trying to load resource from location={}, with baseLocation={}",
                new Object[] { location, baseLocation });

        URI fullLocation = makeFullLocation(location, baseLocation);

        return fullLocation.toString();
    }

    /**
     * {@inheritDoc}
     */
    private Resource load(String location, String baseLocation, Node... args) throws ResourceLoadException {
        log.debug("Trying to load resource from location={}, with baseLocation={}",
                new Object[] { location, baseLocation });

        String fullLocation = PathUtils.resolveUrl(location, baseLocation);
        ru.histone.evaluator.functions.global.URI uri = PathUtils.parseURI(fullLocation);

        Resource resource = null;
        if (baseLocation == null && uri.getScheme() == null) {
            throw new ResourceLoadException("Base HREF is empty and resource location is not absolute!");
        }
        if ("file".equals(uri.getScheme())) {
            resource = loadFileResource(makeFullLocation(location, baseLocation));
        } else if ("http".equals(uri.getScheme())) {
            resource = loadHttpResource(makeFullLocation(location, baseLocation), args);
        } else if ("data".equals(uri.getScheme())) {
            resource = loadDataResource(fullLocation);
        } else {
            throw new ResourceLoadException(
                    String.format("Unsupported scheme for resource loading: '%s'", uri.getScheme()));
        }

        return resource;
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public Resource load(String href, String baseHref, String[] contentTypes, Node... args)
            throws ResourceLoadException {
        final Set<String> contentTypesSet = new HashSet<String>();
        contentTypesSet.addAll(Arrays.asList(contentTypes));
        Resource resource = null;
        if (contentTypesSet.contains(ContentType.TEXT) || contentTypesSet.contains(ContentType.AST)) {
            resource = load(href, baseHref, args);
        } else {
            throw new UnsupportedContentTypeException(contentTypes, getClass());
        }

        if (contentTypesSet.contains(ContentType.AST)) {
            String content = null;
            ArrayNode ast = null;

            try {
                if (resource instanceof StringResource) {
                    content = ((StringResource) resource).getContent();
                } else if (resource instanceof StreamResource) {
                    content = IOUtils.toString(((StreamResource) resource).getContent());
                } else {
                    throw new ResourceLoadException("Unsupported resource class:" + resource.getClass());
                }
                ast = parser.parse(content);
            } catch (IOException e) {
                throw new ResourceLoadException("Error reading resource InputStream", e);
            } catch (ParserException e) {
                throw new ResourceLoadException("Error parsing resource", e);
            }

            String fullLocation = PathUtils.resolveUrl(href, baseHref);
            resource = new AstResource(ast, fullLocation);
        }

        return resource;

    }

    private StreamResource loadFileResource(URI location) {
        InputStream stream = null;

        File file = new File(location);
        if (file.exists() && file.isFile() && file.canRead()) {
            try {
                stream = new FileInputStream(file);
            } catch (FileNotFoundException e) {
                throw new ResourceLoadException("File not found", e);
            }
        } else {
            throw new ResourceLoadException(String.format("Can't read file '%s'", location.toString()));
        }

        BOMInputStream bomStream = null;
        try {
            bomStream = new BOMInputStream(stream);
            if (bomStream.getBOM() != BOMInputStream.BOM.NONE) {
                bomStream.skipBOM();
            }
        } catch (IOException e) {
            throw new ResourceLoadException(
                    String.format("Error with BOMInputStream for file '%s'", location.toString()));
        }

        return new StreamResource(bomStream, location.toString(), ContentType.TEXT, file.lastModified());
    }

    private StreamResource loadHttpResource(URI location, Node[] args) {
        URI newLocation = URI.create(location.toString().replace("#fragment", ""));
        final Map<Object, Node> requestMap = args != null && args.length != 0
                && args[0] instanceof ObjectHistoneNode ? ((ObjectHistoneNode) args[0]).getElements()
                        : new HashMap<Object, Node>();
        Node methodNode = requestMap.get("method");
        final String method = methodNode != null && methodNode.isString()
                && methodNode.getAsString().getValue().length() != 0
                        ? requestMap.get("method").getAsString().getValue()
                        : "GET";
        final Map<String, String> headers = new HashMap<String, String>();
        if (requestMap.containsKey("headers")) {
            for (Map.Entry<Object, Node> en : requestMap.get("headers").getAsObject().getElements().entrySet()) {
                String value = null;
                if (en.getValue().isUndefined())
                    value = "undefined";
                else
                    value = en.getValue().getAsString().getValue();
                headers.put(en.getKey().toString(), value);
            }
        }

        final Map<String, String> filteredHeaders = filterRequestHeaders(headers);
        final Node data = requestMap.containsKey("data") ? (Node) requestMap.get("data") : null;

        // Prepare request
        HttpRequestBase request = new HttpGet(newLocation);
        if ("POST".equalsIgnoreCase(method)) {
            request = new HttpPost(newLocation);
        } else if ("PUT".equalsIgnoreCase(method)) {
            request = new HttpPut(newLocation);
        } else if ("DELETE".equalsIgnoreCase(method)) {
            request = new HttpDelete(newLocation);
        } else if ("TRACE".equalsIgnoreCase(method)) {
            request = new HttpTrace(newLocation);
        } else if ("OPTIONS".equalsIgnoreCase(method)) {
            request = new HttpOptions(newLocation);
        } else if ("PATCH".equalsIgnoreCase(method)) {
            request = new HttpPatch(newLocation);
        } else if ("HEAD".equalsIgnoreCase(method)) {
            request = new HttpHead(newLocation);
        } else if (method != null && !"GET".equalsIgnoreCase(method)) {
            return new StreamResource(null, location.toString(), ContentType.TEXT);
        }

        for (Map.Entry<String, String> en : filteredHeaders.entrySet()) {
            request.setHeader(en.getKey(), en.getValue());
        }
        if (("POST".equalsIgnoreCase(method) || "PUT".equalsIgnoreCase(method)) && data != null) {
            String stringData = null;
            String contentType = filteredHeaders.get("content-type") == null ? ""
                    : filteredHeaders.get("content-type");
            if (data.isObject()) {
                stringData = ToQueryString.toQueryString(data.getAsObject(), null, "&");
                contentType = "application/x-www-form-urlencoded";
            } else {
                stringData = data.getAsString().getValue();
            }
            if (stringData != null) {
                StringEntity se;
                try {
                    se = new StringEntity(stringData);
                } catch (UnsupportedEncodingException e) {
                    throw new ResourceLoadException(String.format("Can't encode data '%s'", stringData));
                }
                ((HttpEntityEnclosingRequestBase) request).setEntity(se);
            }
            request.setHeader("Content-Type", contentType);
        }
        if (request.getHeaders("content-Type").length == 0) {
            request.setHeader("Content-Type", "");
        }

        // Execute request
        HttpClient client = new DefaultHttpClient(httpClientConnectionManager);
        ((AbstractHttpClient) client).setRedirectStrategy(new RedirectStrategy());
        InputStream input = null;
        try {
            HttpResponse response = client.execute(request);
            input = response.getEntity() == null ? null : response.getEntity().getContent();
        } catch (IOException e) {
            throw new ResourceLoadException(String.format("Can't load resource '%s'", location.toString()));
        } finally {
        }
        return new StreamResource(input, location.toString(), ContentType.TEXT);
    }

    private Resource loadDataResource(String location) {

        Resource resource = null;

        if (!location.matches("data:(.*);base64,(.*)")) {
            return null;
        }
        URI uri = makeFullLocation(location, "");
        String[] stringUri = uri.getSchemeSpecificPart().split(",");

        if (stringUri.length > 1) {
            String toEncode = stringUri[1];
            if (stringUri[0].contains("base64")) {
                InputStream stream = new ByteArrayInputStream(Base64.decodeBase64(toEncode.getBytes()));
                resource = new StreamResource(stream, location.toString(), ContentType.TEXT);
            } else {
                resource = new StringResource(toEncode, location.toString(), ContentType.TEXT);
            }
        } else {
            resource = new StringResource("", location.toString(), ContentType.TEXT);
        }

        return resource;
    }

    private Map<String, String> filterRequestHeaders(Map<String, String> requestHeaders) {
        String[] prohibited = { "accept-charset", "accept-encoding", "access-control-request-headers",
                "access-control-request-method", "connection", "content-length", "cookie", "cookie2",
                "content-transfer-encoding", "date", "expect", "host", "keep-alive", "origin", "referer", "te",
                "trailer", "transfer-encoding", "upgrade", "user-agent", "via" };
        Map<String, String> headers = new HashMap<String, String>();
        for (Map.Entry<String, String> entry : requestHeaders.entrySet()) {
            if (entry.getValue() == null)
                continue;
            String name = entry.getKey().toLowerCase();
            if (name.indexOf("sec-") == 0)
                continue;
            if (name.indexOf("proxy-") == 0)
                continue;
            if (Arrays.asList(prohibited).contains(name))
                continue;
            headers.put(entry.getKey(), entry.getValue());
        }
        return headers;
    }

    public URI makeFullLocation(String location, String baseLocation) {
        if (location == null) {
            throw new ResourceLoadException("Resource location is undefined!");
        }

        URI locationURI = URI.create(location);

        if (baseLocation == null && !locationURI.isAbsolute()) {
            throw new ResourceLoadException("Base HREF is empty and resource location is not absolute!");
        }

        if (baseLocation != null) {
            baseLocation = baseLocation.replace("\\", "/");
            baseLocation = baseLocation.replace("file://", "file:/");
        }
        URI baseLocationURI = (baseLocation != null) ? URI.create(baseLocation) : null;

        if (!locationURI.isAbsolute() && baseLocation != null) {
            locationURI = baseLocationURI.resolve(locationURI.normalize());
        }

        if (!locationURI.isAbsolute()) {
            throw new ResourceLoadException("Resource location is not absolute!");
        }

        return locationURI;
    }

    public ClientConnectionManager getHttpClientConnectionManager() {
        return httpClientConnectionManager;
    }

    public void setHttpClientConnectionManager(ClientConnectionManager httpClientConnectionManager) {
        this.httpClientConnectionManager = httpClientConnectionManager;
    }

    private class RedirectStrategy extends DefaultRedirectStrategy {
        @Override
        public boolean isRedirected(final HttpRequest request, final HttpResponse response,
                final HttpContext context) throws ProtocolException {
            if (request == null) {
                throw new IllegalArgumentException("HTTP request may not be null");
            }
            if (response == null) {
                throw new IllegalArgumentException("HTTP response may not be null");
            }

            int statusCode = response.getStatusLine().getStatusCode();
            String method = request.getRequestLine().getMethod();
            Header locationHeader = response.getFirstHeader("location");
            if (301 <= statusCode && statusCode <= 399) {
                return true;
            } else {
                return false;
            }
        }
    }

}