4

I have a class that has two methods that have a lot of duplicate code but the bit that's unique is in the middle of the whole thing. From my research I think I should be doing the "Execute around method" pattern but I can't find a resource that I can follow as they all seem to use code I can't replicate.

I have two methods, apiPost and apiGet, which I've pasted below. I've wrapped the unique parts of these methods with comments showing where the unique section starts and ends:

/**
 * Class that handles authorising the connection and handles posting and getting data
 *
 * @version     %I%, %G%
 * @since       1.0
 */
public class CallHandler {
    private static PropertyLoader props = PropertyLoader.getInstance();
    final static int MAX = props.getPropertyAsInteger(props.MAX_REQUESTS);
    private final Logger log = LoggerFactory.getLogger(CallHandler.class);
    private final static String POST = "POST";
    private final static String GET = "GET";

    /**
     * Makes a POST call to the API URL provided and returns the JSON response as a string
     * http://stackoverflow.com/questions/15570656/how-to-send-request-payload-to-rest-api-in-java
     *
     * @param urlString     the API URL to send the data to, as a string
     * @param payload       the serialised JSON payload string
     * @return              and value returned as a JSON string, ready to be deserialised
     */
    public String apiPost(String urlString, String payload) {
        boolean keepGoing = true;
        int tries = 0;

        String line;
        StringBuilder jsonString = new StringBuilder();

        log.debug("Making API Call: {}", urlString);

        while (keepGoing && tries < MAX) {
            tries++;
            try {
                URL url = new URL(urlString);

                HttpURLConnection connection = (HttpURLConnection) url.openConnection();

                // UNIQUE CODE START
                prepareConnection(connection, POST);
                OutputStreamWriter writer = new OutputStreamWriter(connection.getOutputStream(), "UTF-8");
                writer.write(payload);
                writer.close();
                // UNIQUE CODE END

                BufferedReader br = new BufferedReader(new InputStreamReader(connection.getInputStream()));
                while ((line = br.readLine()) != null) {
                    jsonString.append(line);
                }
                br.close();
                connection.disconnect();
                keepGoing = false;
            } catch (Exception e) {
                log.warn("Try #{}. Error posting: {}", tries, e.getMessage());
                log.warn("Pausing for 1 second then trying again...");
                try {
                    TimeUnit.SECONDS.sleep(1);
                } catch (InterruptedException f) {
                    log.warn("Sleeping has been interrupted: {}", f.getMessage());
                }
            }
        }
        return jsonString.toString();
    }

    /**
     * Makes a GET call to the API URL provided and returns the JSON response as a string
     * http://stackoverflow.com/questions/2793150/using-java-net-urlconnection-to-fire-and-handle-http-requests
     *
     * @param urlString     the API URL to request the data from, as a string
     * @return              the json response as a string, ready to be deserialised
     */
    public String apiGet(String urlString) {
        boolean keepGoing = true;
        int tries = 0;

        String line;
        StringBuilder jsonString = new StringBuilder();

        log.debug("Making API Call: {}", urlString);

        while (keepGoing && tries < MAX) {
            tries++;
            try {
                URL url = new URL(urlString);

                HttpURLConnection connection = (HttpURLConnection) url.openConnection();

                // UNIQUE CODE START
                prepareConnection(connection, GET);
                connection.connect();
                // UNIQUE CODE END

                BufferedReader br = new BufferedReader(new InputStreamReader(connection.getInputStream()));
                while ((line = br.readLine()) != null) {
                    jsonString.append(line);
                }
                br.close();
                connection.disconnect();
                keepGoing = false;
            } catch (Exception e) {
                log.warn("Try #{}. Error getting from API: {}", tries, e.getMessage());
                log.warn("Pausing for 1 second then trying again...");
                try {
                    TimeUnit.SECONDS.sleep(1);
                } catch (InterruptedException f) {
                    log.warn("Sleeping has been interrupted: {}", f.getMessage());
                }
            }
        }
        return jsonString.toString();
    }

    /**
     * Prepares the HTTP Url connection depending on whether this is a POST or GET call
     *
     * @param connection    the connection to prepare
     * @param method        whether the call is a POST or GET call
     */
    private void prepareConnection(HttpURLConnection connection, String method) {
        String charset = "UTF-8";
        try {
            connection.setRequestMethod(method);
            if (method.equals(GET)) {
                connection.setRequestProperty("Accept-Charset", charset);
            } else if (method.equals(POST)) {
                connection.setDoInput(true);
                connection.setDoOutput(true);
                connection.setRequestProperty("Content-Type", "application/json; charset=" + charset);
            }
            connection.setRequestProperty("Accept", "application/json");
            connection.setRequestProperty("Authorization", "Bearer " + apiKey);
        } catch (Exception e) {
            log.error("Error preparing HTTP URL connection: {}", e.getMessage());
            throw new RuntimeException(e.getMessage());
        }
    }

Can I use the "Execute around method" pattern to save on code duplication here? If so could someone help me figure out how to refactor this code to make use of it. If this is the wrong way to go about it could someone suggest a smart alternative?

SBmore
  • 551
  • 3
  • 10

1 Answers1

5

It can be done by extracting "unique" code into special worker. More specifically for example, you can use lambda expressions:

public String apiPost(String urlString, String payload) {
    return commonMethod(urlString, payload, (connection) -> {
        // UNIQUE CODE START
        prepareConnection(connection, POST);
        OutputStreamWriter writer = new OutputStreamWriter(connection.getOutputStream(), "UTF-8");
        writer.write(payload);
        writer.close();
        // UNIQUE CODE END
    });
}

interface ConnectionWorker {
    void run(HttpURLConnection connection) throws IOException;
}

public String commonMethod(String urlString, String payload, ConnectionWorker worker) {
    boolean keepGoing = true;
    int tries = 0;

    String line;
    StringBuilder jsonString = new StringBuilder();

    log.debug("Making API Call: {}", urlString);

    while (keepGoing && tries < MAX) {
        tries++;
        try {
            URL url = new URL(urlString);

            HttpURLConnection connection = (HttpURLConnection) url.openConnection();

            worker.run(connection);

            BufferedReader br = new BufferedReader(new InputStreamReader(connection.getInputStream()));
            while ((line = br.readLine()) != null) {
                jsonString.append(line);
            }
            br.close();
            connection.disconnect();
            keepGoing = false;
        } catch (Exception e) {
            log.warn("Try #{}. Error posting: {}", tries, e.getMessage());
            log.warn("Pausing for 1 second then trying again...");
            try {
                TimeUnit.SECONDS.sleep(1);
            } catch (InterruptedException f) {
                log.warn("Sleeping has been interrupted: {}", f.getMessage());
            }
        }
    }
    return jsonString.toString();
}

UPDATE: In case if you can not use java 8 and lambda, you can always switch to creating anonymous class:

    return commonMethod(urlString, payload, new ConnectionWorker() {
        @Override
        public void run(HttpURLConnection connection) throws IOException {
            // UNIQUE CODE START
            CallHandler.this.prepareConnection(connection, POST);
            OutputStreamWriter writer = new OutputStreamWriter(connection.getOutputStream(), "UTF-8");
            writer.write(payload);
            writer.close();
            // UNIQUE CODE END
        }
    }); 
Andremoniy
  • 31,241
  • 14
  • 110
  • 218
  • Thanks for the reply @Andremoniy I think I'm starting to understand it (though I need to do some reading on Lambda). Is there anyway to do this without Lambda as I'm developing for the Google App Engine and I don't think it supports Java 8 :S – SBmore Aug 02 '16 at 09:25
  • @SBmore Yep, sure, look at my edit, you can use anonymous class – Andremoniy Aug 02 '16 at 09:41
  • Thank you so much @Andremoniy, this helped greatly and I learned a lot. – SBmore Aug 03 '16 at 08:42