com.adobe.ags.curly.controller.ActionRunner.java Source code

Java tutorial

Introduction

Here is the source code for com.adobe.ags.curly.controller.ActionRunner.java

Source

/* 
 * Copyright 2015 Adobe.
 *
 * 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 com.adobe.ags.curly.controller;

import com.adobe.ags.curly.ApplicationState;
import com.adobe.ags.curly.ConnectionManager;
import static com.adobe.ags.curly.Messages.*;
import com.adobe.ags.curly.model.ActionResult;
import com.adobe.ags.curly.model.ActionUtils;
import com.adobe.ags.curly.xml.Action;
import com.google.gson.internal.LinkedTreeMap;
import java.io.File;
import java.io.UnsupportedEncodingException;
import java.net.URISyntaxException;
import java.net.URLEncoder;
import java.text.ParseException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.Set;
import java.util.function.Function;
import java.util.logging.Level;
import java.util.logging.Logger;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import javafx.beans.property.BooleanProperty;
import javafx.beans.property.SimpleBooleanProperty;
import javafx.beans.property.SimpleStringProperty;
import javafx.beans.property.StringProperty;
import org.apache.http.NameValuePair;
import org.apache.http.client.entity.UrlEncodedFormEntity;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpDelete;
import org.apache.http.client.methods.HttpEntityEnclosingRequestBase;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.methods.HttpHead;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.client.methods.HttpPut;
import org.apache.http.client.methods.HttpUriRequest;
import org.apache.http.entity.ContentType;
import org.apache.http.entity.FileEntity;
import org.apache.http.entity.mime.MultipartEntityBuilder;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.message.BasicNameValuePair;
import org.apache.http.util.EntityUtils;

public class ActionRunner implements Runnable {

    public static final String UTF8 = "UTF-8";

    public static enum HttpMethod {
        GET, POST, DELETE, HEAD, PUT, TRACE, CONNECT, OPTIONS
    };

    Map<String, List<String>> postVariables = new LinkedTreeMap<>();
    Map<String, List<String>> getVariables = new LinkedTreeMap<>();
    Map<String, String> requestHeaders = new LinkedTreeMap<>();
    Action action;
    String URL;
    HttpMethod httpMethod = HttpMethod.GET;
    String putFile = "";
    boolean httpMethodExplicitlySet = false;
    boolean multipart = false;
    ActionResult response;
    Function<Function<CloseableHttpClient, Optional<Exception>>, Optional<Exception>> processor;

    public ActionRunner(Function<Function<CloseableHttpClient, Optional<Exception>>, Optional<Exception>> processor,
            Action action, Map<String, String> variables) throws ParseException {
        this.processor = processor;
        parseCommand(action);
        applyVariables(variables);
        response = new ActionResult(this);
    }

    public Action getAction() {
        return action;
    }

    @Override
    public void run() {
        if (!ApplicationState.getInstance().runningProperty().get()) {
            response.setException(new Exception(ApplicationState.getMessage(ACTIVITY_TERMINATED)));
            return;
        }
        response.started().set(true);
        response.updateProgress(0.5);

        if (action.getDelay() > 0) {
            try {
                Thread.sleep(action.getDelay());
            } catch (InterruptedException ex) {
                response.setException(ex);
                return;
            }
        }

        HttpUriRequest request;
        try {
            switch (httpMethod) {
            case GET:
                request = new HttpGet(getURL());
                break;
            case HEAD:
                request = new HttpHead(getURL());
                break;
            case DELETE:
                request = new HttpDelete(getURL());
                break;
            case POST:
                request = new HttpPost(getURL());
                addPostParams((HttpPost) request);
                break;
            case PUT:
                request = new HttpPut(getURL());
                ((HttpPut) request).setEntity(new FileEntity(new File(putFile)));
                break;
            default:
                throw new UnsupportedOperationException(
                        ApplicationState.getMessage(UNSUPPORTED_METHOD_ERROR) + ": " + httpMethod.name());
            }

            addHeaders(request);
            Optional<Exception> ex = processor.apply((CloseableHttpClient client) -> {
                try (CloseableHttpResponse httpResponse = client.execute(request, ConnectionManager.getContext())) {
                    response.processHttpResponse(httpResponse, action.getResultType());
                    EntityUtils.consume(httpResponse.getEntity());
                    return Optional.empty();
                } catch (Exception e) {
                    return Optional.of(e);
                }
            });
            if (ex.isPresent()) {
                throw ex.get();
            }
        } catch (Exception ex) {
            Logger.getLogger(ActionRunner.class.getName()).log(Level.SEVERE, null, ex);
            response.setException(ex);
        } finally {
            response.updateProgress(1);
        }
    }

    private String getURL() throws URISyntaxException {
        StringBuilder urlBuilder = new StringBuilder();
        String URI = URL.contains("?") ? URL.substring(0, URL.indexOf('?')) : URL;
        URI = URI.replaceAll("\\s", "%20");
        urlBuilder.append(URI);
        final BooleanProperty hasQueryString = new SimpleBooleanProperty(URL.contains("?"));
        getVariables.forEach((key, values) -> {
            if (values != null) {
                values.forEach(value -> {
                    try {
                        urlBuilder.append(hasQueryString.get() ? "&" : "?").append(URLEncoder.encode(key, UTF8))
                                .append("=").append(URLEncoder.encode(value != null ? value : "", UTF8));
                        hasQueryString.set(false);
                    } catch (UnsupportedEncodingException ex) {
                        Logger.getLogger(ActionRunner.class.getName()).log(Level.SEVERE, null, ex);
                    }
                });
            }
        });
        return urlBuilder.toString();
    }

    private void addHeaders(HttpUriRequest request) {
        requestHeaders.forEach(request::setHeader);
    }

    private void addPostParams(HttpEntityEnclosingRequestBase request) throws UnsupportedEncodingException {
        final MultipartEntityBuilder multipartBuilder = MultipartEntityBuilder.create();
        List<NameValuePair> formParams = new ArrayList<>();
        postVariables.forEach((name, values) -> values.forEach(value -> {
            if (multipart) {
                if (value.startsWith("@")) {
                    File f = new File(value.substring(1));
                    multipartBuilder.addBinaryBody(name, f, ContentType.DEFAULT_BINARY, f.getName());
                } else {
                    multipartBuilder.addTextBody(name, value);
                }
            } else {
                formParams.add(new BasicNameValuePair(name, value));
            }
        }));
        if (multipart) {
            request.setEntity(multipartBuilder.build());
        } else {
            request.setEntity(new UrlEncodedFormEntity(formParams));
        }
    }

    private void parseCommand(Action action) throws ParseException {
        this.action = action;
        String commandStr = tokenizeParameters(action.getCommand());

        List<String> parts = splitByUnquotedSpaces(commandStr);
        URL = detokenizeParameters(parts.remove(parts.size() - 1));
        int offset = 0;
        for (int i = 0; i < parts.size(); i++) {
            String part = parts.get(i);
            if (part.startsWith("-")) {
                if (part.length() == 2 && i < parts.size() - 1) {
                    if (parseCmdParam(part.charAt(1), parts.get(i + 1), offset)) {
                        i++;
                    }
                } else {
                    parseCmdParam(part.charAt(1), part.substring(2), offset);
                }
            } else {
                throw new ParseException(ApplicationState.getMessage(UNKNOWN_PARAMETER) + ": " + part, offset);
            }
            offset += part.length() + 1;
        }
    }

    public static List<String> splitByUnquotedSpaces(String str) {
        List<String> list = new ArrayList<>();
        String token = "";
        boolean insideQuote = false;
        for (int i = 0; i < str.length(); i++) {
            char c = str.charAt(i);

            switch (c) {
            case '"':
                insideQuote = !insideQuote;
                break;
            case ' ':
            case '\t':
            case '\n':
                if (!insideQuote) {
                    if (!token.isEmpty()) {
                        list.add(token);
                        token = "";
                    }
                    break;
                }
            default:
                token += c;
            }
        }
        if (!token.isEmpty()) {
            list.add(token);
        }
        return list;
    }

    private String tokenizeParameters(String str) {
        Set<String> variableTokens = ActionUtils.getVariableNames(action);
        int tokenCounter = 0;
        for (String var : variableTokens) {
            String varPattern = Pattern.quote("${") + var + "(\\|.*?)?" + Pattern.quote("}");
            str = str.replaceAll(varPattern, Matcher.quoteReplacement("${" + (tokenCounter++) + "}"));
        }
        return str;
    }

    private String detokenizeParameters(String str) {
        Set<String> variableTokens = ActionUtils.getVariableNames(action);
        int tokenCounter = 0;
        for (String var : variableTokens) {
            str = str.replaceAll(Pattern.quote("${" + (tokenCounter++) + "}"),
                    Matcher.quoteReplacement("${" + var + "}"));
        }
        return str;
    }

    private boolean parseCmdParam(char command, String param, int offset) throws ParseException {
        switch (command) {
        case 'F':
            httpMethod = HttpMethod.POST;
            httpMethodExplicitlySet = true;
        case 'd':
            Map<String, List<String>> vars = postVariables;
            if (!httpMethodExplicitlySet) {
                httpMethod = HttpMethod.POST;
            } else if (httpMethod != HttpMethod.POST) {
                vars = getVariables;
            }
            int equals = param.indexOf('=');
            if (equals > -1) {
                String fieldName = detokenizeParameters(param.substring(0, equals));
                String value = equals < param.length() - 1 ? detokenizeParameters(param.substring(equals + 1))
                        : null;
                if (command == 'F' && value != null && value.startsWith("@")) {
                    httpMethod = HttpMethod.POST;
                    multipart = true;
                }
                if (!vars.containsKey(fieldName)) {
                    vars.put(fieldName, new ArrayList<>());
                }
                vars.get(fieldName).add(value);
            } else {
                throw new ParseException(ApplicationState.getMessage(MISSING_NVP_FORM_ERROR), offset + 1);
            }
            return true;
        case 'T':
            httpMethod = HttpMethod.PUT;
            multipart = false;
            putFile = detokenizeParameters(param);
            return true;
        case 'X':
            try {
                httpMethod = HttpMethod.valueOf(param.toUpperCase());
            } catch (IllegalArgumentException ex) {
                throw new ParseException(ApplicationState.getMessage(UNKNOWN_METHOD_ERROR) + " " + param,
                        offset + 1);
            }
            return true;
        case 'h':
            String[] nvp = param.split(":\\s*");
            if (nvp.length != 2) {
                throw new ParseException(ApplicationState.getMessage(MISSING_NVP_HEADER_ERROR), offset + 1);
            }
            requestHeaders.put(detokenizeParameters(nvp[0]), detokenizeParameters(nvp[1]));
            return true;
        case 'e':
            requestHeaders.put("referer", detokenizeParameters(param));
            return true;
        case 'u':
            // ignored parameterized options
            return true;
        case 'G':
            httpMethodExplicitlySet = true;
            httpMethod = HttpMethod.GET;
        case 'S':
        case '#':
        case 'v':
            //ignored no-parameter flags
            return false;
        default:
            throw new ParseException(ApplicationState.getMessage(UNKNOWN_PARAMETER) + ": " + command, offset);
        }
    }

    private void applyVariables(Map<String, String> variables) {
        Set<String> variableTokens = ActionUtils.getVariableNames(action);
        variableTokens.forEach((String originalName) -> {
            String[] parts = originalName.split("\\|");
            String var = parts[0];
            String replaceVar = Pattern.quote("${" + originalName + "}");
            String replace = variables.get(var) == null ? "" : variables.get(var);
            URL = URL.replaceAll(replaceVar, Matcher.quoteReplacement(replace));
            putFile = putFile.replaceAll(replaceVar, Matcher.quoteReplacement(replace));
        });
        applyMultiVariablesToMap(variables, postVariables);
        applyMultiVariablesToMap(variables, getVariables);
        applyVariablesToMap(variables, requestHeaders);
    }

    private void applyVariablesToMap(Map<String, String> variables, Map<String, String> target) {
        Set<String> variableTokens = ActionUtils.getVariableNames(action);

        Set removeSet = new HashSet<>();
        Map<String, String> newValues = new HashMap<>();

        target.forEach((paramName, paramValue) -> {
            StringProperty paramNameProperty = new SimpleStringProperty(paramName);
            variableTokens.forEach((String originalName) -> {
                String[] variableNameParts = originalName.split("\\|");
                String variableName = variableNameParts[0];
                String variableNameMatchPattern = Pattern.quote("${" + originalName + "}");
                String val = variables.get(variableName);
                if (val == null) {
                    val = "";
                }
                String variableValue = Matcher.quoteReplacement(val);
                //----
                String newParamValue = newValues.containsKey(paramNameProperty.get())
                        ? newValues.get(paramNameProperty.get())
                        : paramValue;
                String newParamName = paramNameProperty.get().replaceAll(variableNameMatchPattern, variableValue);
                paramNameProperty.set(newParamName);
                newParamValue = newParamValue.replaceAll(variableNameMatchPattern, variableValue);
                if (!newParamName.equals(paramName) || !newParamValue.equals(paramValue)) {
                    removeSet.add(paramNameProperty.get());
                    removeSet.add(paramName);
                    newValues.put(newParamName, newParamValue);
                }
            });
        });
        target.keySet().removeAll(removeSet);
        target.putAll(newValues);
    }

    private void applyMultiVariablesToMap(Map<String, String> variables, Map<String, List<String>> target) {
        Set<String> variableTokens = ActionUtils.getVariableNames(action);

        Map<String, List<String>> newValues = new HashMap<>();
        Set removeSet = new HashSet<>();

        target.forEach((paramName, paramValues) -> {
            StringProperty paramNameProperty = new SimpleStringProperty(paramName);
            variableTokens.forEach((String originalName) -> {
                String[] variableNameParts = originalName.split("\\|");
                String variableName = variableNameParts[0];
                String variableNameMatchPattern = Pattern.quote("${" + originalName + "}");
                String val = variables.get(variableName);
                if (val == null) {
                    val = "";
                }
                String variableValue = Matcher.quoteReplacement(val);
                String newParamName = paramNameProperty.get().replaceAll(variableNameMatchPattern, variableValue);
                removeSet.add(paramNameProperty.get());
                removeSet.add(paramName);
                if (newValues.get(paramNameProperty.get()) == null) {
                    newValues.put(paramNameProperty.get(), new ArrayList<>(paramValues.size()));
                }
                if (newValues.get(newParamName) == null) {
                    newValues.put(newParamName, new ArrayList<>(paramValues.size()));
                }
                List<String> newParamValues = newValues.get(paramNameProperty.get());
                for (int i = 0; i < paramValues.size(); i++) {
                    String newParamValue = newParamValues != null && newParamValues.size() > i
                            && newParamValues.get(i) != null ? newParamValues.get(i) : paramValues.get(i);

                    // fix for removing JCR values (setting them to an empty
                    // string deletes them from the JCR)
                    if (null == newParamValue) {
                        newParamValue = "";
                    }

                    newParamValue = newParamValue.replaceAll(variableNameMatchPattern, variableValue);
                    if (newParamName.contains("/") && newParamValue.equals("@" + newParamName)) {
                        // The upload name should actually be the file name, not the full path of the file.
                        removeSet.add(newParamName);
                        newValues.remove(newParamName);
                        newParamName = newParamName.substring(newParamName.lastIndexOf("/") + 1);
                        newValues.put(newParamName, newParamValues);
                    }
                    if (newValues.get(newParamName).size() == i) {
                        newValues.get(newParamName).add(newParamValue);
                    } else {
                        newValues.get(newParamName).set(i, newParamValue);
                    }
                }
                if (!paramNameProperty.get().equals(newParamName)) {
                    newValues.remove(paramNameProperty.get());
                }
                paramNameProperty.set(newParamName);
            });
        });
        target.keySet().removeAll(removeSet);
        target.putAll(newValues);
    }
}