com.codealot.url2text.Response.java Source code

Java tutorial

Introduction

Here is the source code for com.codealot.url2text.Response.java

Source

package com.codealot.url2text;

import static com.codealot.url2text.Constants.*;

import java.io.ByteArrayOutputStream;
import java.io.Closeable;
import java.io.IOException;
import java.io.Reader;
import java.io.StringReader;
import java.text.ParseException;
import java.util.ArrayList;
import java.util.Date;
import java.util.Iterator;
import java.util.List;
import java.util.Objects;

import org.apache.commons.lang3.time.DateFormatUtils;

import com.codealot.url2text.Constants.OutputFormat;
import com.fasterxml.jackson.core.JsonFactory;
import com.fasterxml.jackson.core.JsonGenerationException;
import com.fasterxml.jackson.core.JsonGenerator;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;

/**
 * Class to encapsulate the result of fetching and converting a url's content.
 * Consists of a bunch of properties plus asFormat(), toString() and toJson()
 * methods.
 * 
 * <p>
 * Note: getters will never return null.
 * 
 * <p>
 * 
 * @author jacobsp
 * 
 *         <p>
 *         Copyright (C) 2014 Codealot Limited.
 * 
 *         <p>
 *         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
 *
 *         <p>
 *         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.
 */
public class Response implements Closeable, AutoCloseable {
    // transaction metadata
    private String requestPage = STR_NOT_SET;
    private String landingPage = STR_NOT_SET;
    private int status = INT_NOT_SET;
    private String statusMessage = STR_NOT_SET;
    private Date fetchDate = new Date();
    private long fetchDuration = LONG_NOT_SET;
    private String contentTitle = STR_NOT_SET;
    private String contentType = STR_NOT_SET;
    private String contentCharset = STR_NOT_SET;
    private long contentLength = LONG_NOT_SET;
    private String etag = STR_NOT_SET;
    private String lastModified = STR_NOT_SET;
    private long conversionDuration = 0L;

    // optional content
    private List<NameAndValue> responseHeaders = new ArrayList<>();
    private List<NameAndValue> contentMetadata = new ArrayList<>();

    // text vars
    private Reader textReader = new StringReader(STR_NOT_SET);
    private String text = null; // used to make getText(), toString() and
                                // toJson() repeatable.

    // operational flag
    private boolean textSupplied = false;

    // constructor
    public Response() {
        // default
    }

    /**
     * Constructor to build an instance from the output of {@link #toJson()}.
     * 
     * @param json
     * @throws IOException
     * @throws JsonProcessingException
     * @throws ParseException 
     */
    public Response(final String json) throws JsonProcessingException, IOException, ParseException {
        final ObjectMapper mapper = new ObjectMapper();
        final JsonNode rootNode = mapper.readTree(json);

        final JsonNode transactionNode = rootNode.get(HDR_TRANSACTION_METADATA);
        this.requestPage = transactionNode.get(HDR_REQUEST_PAGE).textValue();
        this.landingPage = transactionNode.get(HDR_LANDING_PAGE).textValue();
        this.status = transactionNode.get(HDR_STATUS).asInt();
        this.statusMessage = transactionNode.get(HDR_STATUS_MESSAGE).textValue();
        this.fetchDate = DateFormatUtils.ISO_DATETIME_TIME_ZONE_FORMAT
                .parse(transactionNode.get(HDR_FETCH_DATE).textValue());
        this.fetchDuration = transactionNode.get(HDR_FETCH_DURATION).asLong();
        this.contentType = transactionNode.get(HDR_CONTENT_TYPE).textValue();
        this.contentCharset = transactionNode.get(HDR_CONTENT_CHARSET).textValue();
        this.contentLength = transactionNode.get(HDR_CONTENT_LENGTH).asLong();
        this.etag = transactionNode.get(HDR_ETAG).textValue();
        this.lastModified = transactionNode.get(HDR_LAST_MODIFIED).textValue();
        this.conversionDuration = transactionNode.get(HDR_CONVERSION_DURATION).asLong();

        final JsonNode headersNode = rootNode.get(HDR_RESPONSE_HEADERS);
        for (final Iterator<String> i = headersNode.fieldNames(); i.hasNext();) {
            final String key = i.next();
            final String value = headersNode.get(key).textValue();
            this.responseHeaders.add(new NameAndValue(key, value));
        }

        final JsonNode metadataNode = rootNode.get(HDR_CONTENT_METADATA);
        for (final Iterator<String> i = metadataNode.fieldNames(); i.hasNext();) {
            final String key = i.next();
            final String value = metadataNode.get(key).textValue();
            this.contentMetadata.add(new NameAndValue(key, value));
        }

        this.textReader = new StringReader(rootNode.get(HDR_CONVERTED_TEXT).textValue());
    }

    /**
     * Renders this object in the given format.
     * 
     * @return
     * @throws Url2TextException
     */
    public String asFormat(final OutputFormat format) throws Url2TextException {
        String result = null;
        if (format == OutputFormat.PLAIN) {
            result = this.toString();
        } else if (format == OutputFormat.JSON) {
            result = this.toJson();
        } else {
            throw new IllegalArgumentException("Format " + format + " not supported.");
        }
        return result;
    }

    /**
     * Closes the text Reader.
     * 
     * @throws IOException
     */
    @Override
    public void close() throws IOException {
        if (this.textReader != null) {
            this.textReader.close();
        }
    }

    @Override
    public int hashCode() {
        try {
            getTextFromReader();
        } catch (Url2TextException e) {
            throw new RuntimeException(e);
        }
        return Objects.hash(this.status, this.statusMessage, this.fetchDate, this.fetchDuration, this.contentLength,
                this.conversionDuration, this.requestPage, this.landingPage, this.contentType, this.contentCharset,
                this.etag, this.lastModified, this.responseHeaders, this.contentMetadata, this.text);
    }

    @Override
    public boolean equals(final Object obj) {
        boolean result = false;
        if (this == obj) {
            result = true;
        } else if (obj == null || obj.getClass() != this.getClass()) {
            result = false;
        } else {
            final Response test = (Response) obj;
            // all content is included in toString(), so this is safe.
            result = test.toString().equals(this.toString());
        }
        return result;
    }

    /**
     * Renders this object as JSON.
     * <p>
     * Beware. This method consumes the internal Reader, creating a buffer of
     * unlimited size.
     * 
     * @return
     * @throws Url2TextException
     */
    public String toJson() throws Url2TextException {

        final JsonFactory jFactory = new JsonFactory();
        final ByteArrayOutputStream destination = new ByteArrayOutputStream();

        try (final JsonGenerator jsonGenerator = jFactory.createGenerator(destination);) {
            jsonGenerator.writeStartObject();

            // transaction metadata
            jsonGenerator.writeFieldName(HDR_TRANSACTION_METADATA);
            jsonGenerator.writeStartObject();

            jsonGenerator.writeStringField(HDR_REQUEST_PAGE, this.requestPage);
            jsonGenerator.writeStringField(HDR_LANDING_PAGE, this.landingPage);
            jsonGenerator.writeNumberField(HDR_STATUS, this.status);
            jsonGenerator.writeStringField(HDR_STATUS_MESSAGE, this.statusMessage);
            jsonGenerator.writeStringField(HDR_FETCH_DATE,
                    DateFormatUtils.ISO_DATETIME_TIME_ZONE_FORMAT.format(this.fetchDate));
            jsonGenerator.writeNumberField(HDR_FETCH_DURATION, this.fetchDuration);
            jsonGenerator.writeStringField(HDR_CONTENT_TYPE, this.contentType);
            jsonGenerator.writeStringField(HDR_CONTENT_CHARSET, this.contentCharset);
            jsonGenerator.writeNumberField(HDR_CONTENT_LENGTH, this.contentLength);
            jsonGenerator.writeStringField(HDR_ETAG, this.etag);
            jsonGenerator.writeStringField(HDR_LAST_MODIFIED, this.lastModified);
            jsonGenerator.writeNumberField(HDR_CONVERSION_DURATION, this.conversionDuration);

            jsonGenerator.writeEndObject();

            // response headers
            if (!this.responseHeaders.isEmpty()) {
                outputNameAndValueArray(jsonGenerator, HDR_RESPONSE_HEADERS, this.responseHeaders);
            }

            // content metadata
            if (!this.contentMetadata.isEmpty()) {
                outputNameAndValueArray(jsonGenerator, HDR_CONTENT_METADATA, this.contentMetadata);
            }

            // text
            jsonGenerator.writeStringField(HDR_CONVERTED_TEXT, this.getText());
            jsonGenerator.writeEndObject();
            jsonGenerator.close();

            String result = destination.toString(UTF_8);
            return result;
        } catch (IOException e) {
            throw new Url2TextException("Error emitting JSON", e);
        }
    }

    /**
     * Convenience method for writing header and metadata lists.
     * 
     * @param jsonGenerator
     * @param header
     * @param array
     * @throws JsonGenerationException
     * @throws IOException
     */
    private void outputNameAndValueArray(final JsonGenerator jsonGenerator, final String header,
            final List<NameAndValue> array) throws JsonGenerationException, IOException {
        jsonGenerator.writeFieldName(header);
        jsonGenerator.writeStartObject();

        for (final NameAndValue nameAndValue : array) {
            jsonGenerator.writeStringField(nameAndValue.getName(), nameAndValue.getValue());
        }
        jsonGenerator.writeEndObject();
    }

    /**
     * Full dump of the response content in plain text format.
     * <p>
     * Beware. This method consumes the internal Reader, creating a buffer of
     * unlimited size.
     */
    @Override
    public String toString() {
        final StringBuilder buffer = new StringBuilder(350);

        buffer.append("################ TRANSACTION METADATA ################");
        buffer.append("\nRequest page     : ").append(this.requestPage);
        buffer.append("\nLanding page     : ").append(this.landingPage);
        buffer.append("\nStatus           : ").append(this.status).append(' ').append(this.statusMessage);
        buffer.append("\nFetch date       : ")
                .append(DateFormatUtils.ISO_DATETIME_TIME_ZONE_FORMAT.format(this.fetchDate));
        buffer.append("\nFetch duration   : ").append(this.fetchDuration).append(" ms");
        buffer.append("\nContent type     : ").append(this.contentType);
        buffer.append("\nContent charset  : ").append(this.contentCharset);
        buffer.append("\nContent length   : ").append(this.contentLength);
        buffer.append("\nEtag             : ").append(this.etag);
        buffer.append("\nLast Modified    : ").append(this.lastModified);
        buffer.append("\nConvert duration : ").append(this.conversionDuration).append(" ms\n\n");

        if (!responseHeaders.isEmpty()) {
            buffer.append("################ RESPONSE HEADERS ####################\n");
            for (final NameAndValue kvp : responseHeaders) {
                buffer.append(kvp.getName()).append(" = ").append(kvp.getValue()).append('\n');
            }
            buffer.append('\n');
        }

        if (!contentMetadata.isEmpty()) {
            buffer.append("################ CONTENT METADATA ####################\n");

            for (final NameAndValue kvp : contentMetadata) {
                buffer.append(kvp.getName()).append(" = ").append(kvp.getValue()).append('\n');
            }
            buffer.append('\n');
        }
        buffer.append("################ CONVERTED TEXT ######################\n");
        try {
            buffer.append(this.getText());
        } catch (Url2TextException e) {
            throw new RuntimeException(e);
        }
        buffer.append('\n');

        return buffer.toString();
    }

    public String getRequestPage() {
        return this.requestPage;
    }

    public void setRequestPage(final String requestPage) {
        this.requestPage = (requestPage == null) ? "" : requestPage;
    }

    public String getLandingPage() {
        return this.landingPage;
    }

    public void setLandingPage(final String landingPage) {
        this.landingPage = (landingPage == null) ? "" : landingPage;
    }

    public int getStatus() {
        return this.status;
    }

    public void setStatus(final int status) {
        this.status = status;
    }

    public String getStatusMessage() {
        return this.statusMessage;
    }

    public void setStatusMessage(final String statusMessage) {
        this.statusMessage = (statusMessage == null) ? "" : statusMessage;
    }

    public Date getFetchDate() {
        return this.fetchDate;
    }

    public void setFetchDate(Date date) {
        this.fetchDate = date;
    }

    public long getFetchDuration() {
        return this.fetchDuration;
    }

    public void setFetchDuration(final long fetchTime) {
        if (fetchTime < 0L) {
            throw new IllegalArgumentException("Fetch time cannot be negative.");
        }
        this.fetchDuration = fetchTime;
    }

    public String getContentType() {
        return this.contentType;
    }

    public void setContentType(final String contentType) {
        this.contentType = (contentType == null) ? "" : contentType;
    }

    public String getContentCharset() {
        return this.contentCharset;
    }

    public void setContentCharset(final String contentCharset) {
        this.contentCharset = (contentCharset == null) ? "" : contentCharset;
    }

    public long getConversionDuration() {
        return this.conversionDuration;
    }

    public void setConversionDuration(final long conversionTime) {
        if (conversionTime < 0L) {
            throw new IllegalArgumentException("Conversion time cannot be negative.");
        }
        this.conversionDuration = conversionTime;
    }

    public String getEtag() {
        return this.etag;
    }

    public void setEtag(final String etag) {
        this.etag = (etag == null) ? "" : etag;
    }

    public String getLastModified() {
        return lastModified;
    }

    public void setLastModified(final String lastModified) {
        this.lastModified = (lastModified == null) ? "" : lastModified;
    }

    public long getContentLength() {
        return this.contentLength;
    }

    public void setContentLength(final long contentLength) {
        this.contentLength = contentLength;
    }

    /**
     * Convenience method which interprets null and empty string as zero.
     * 
     * @param contentLength
     */
    public void setContentLength(final String contentLength) {
        if (contentLength == null || contentLength.length() == 0) {
            // most likley a Transfer-Encoding chunked file
            this.setContentLength(LONG_NOT_SET);
        } else {
            this.setContentLength(Long.parseLong(contentLength));
        }
    }

    public List<NameAndValue> getResponseHeaders() {
        return this.responseHeaders;
    }

    public void setResponseHeaders(final List<NameAndValue> responseHeaders) {
        this.responseHeaders = (responseHeaders == null) ? new ArrayList<NameAndValue>() : responseHeaders;
    }

    public List<NameAndValue> getContentMetadata() {
        return this.contentMetadata;
    }

    public void setContentMetadata(final List<NameAndValue> conversionMetadata) {
        this.contentMetadata = (conversionMetadata == null) ? new ArrayList<NameAndValue>() : conversionMetadata;
    }

    /**
     * Consumes the Reader, which is then closed.
     * <p>
     * Beware. This method consumes the internal Reader, creating a buffer of
     * unlimited size.
     * 
     * @return
     * @throws Url2TextException
     */
    public String getText() throws Url2TextException {
        if (this.text == null) {
            this.getTextFromReader();
        }
        return this.text;
    }

    public Reader getTextReader() {
        if (this.text == null) {
            return this.textReader;
        } else {
            return new StringReader(this.text);
        }
    }

    public void setTextReader(final Reader reader) {
        Objects.requireNonNull(reader, "No Reader supplied.");
        if (this.textSupplied) {
            throw new IllegalStateException("Text or Reader already supplied.");
        }
        this.textReader = reader;
        this.text = null;
        this.textSupplied = true;
    }

    private void getTextFromReader() throws Url2TextException {
        if (this.textReader == null) {
            return;
        }
        final char[] arr = new char[8 * 1024]; // 8K at a time
        final StringBuilder buf = new StringBuilder();
        int numChars;

        try {
            while ((numChars = textReader.read(arr, 0, arr.length)) > 0) {
                buf.append(arr, 0, numChars);
            }
            this.text = buf.toString();
            this.textReader.close();
            this.textReader = null;
        } catch (IOException e) {
            throw new Url2TextException(e);
        }
    }

    public String getContentTitle() {
        return this.contentTitle;
    }

    public void setContentTitle(String contentTitle) {
        this.contentTitle = contentTitle == null ? "" : contentTitle;
    }

}