org.methodize.nntprss.feed.Channel.java Source code

Java tutorial

Introduction

Here is the source code for org.methodize.nntprss.feed.Channel.java

Source

package org.methodize.nntprss.feed;

/* -----------------------------------------------------------
 * nntp//rss - a bridge between the RSS world and NNTP clients
 * Copyright (c) 2002-2007 Jason Brome.  All Rights Reserved.
 *
 * email: nntprss@methodize.org
 * mail:  Jason Brome
 *        PO Box 222-WOB
 *        West Orange
 *        NJ 07052-0222
 * 
 * This file is part of nntp//rss
 * 
 * nntp//rss is free software; you can redistribute it 
 * and/or modify it under the terms of the GNU General 
 * Public License as published by the Free Software Foundation; 
 * either version 2 of the License, or (at your option) any 
 * later version.
 *
 * This program is distributed in the hope that it will be 
 * useful, but WITHOUT ANY WARRANTY; without even the implied 
 * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR 
 * PURPOSE.  See the GNU General Public License for more 
 * details.
 *
 * You should have received a copy of the GNU General Public 
 * License along with this program; if not, write to the 
 * Free Software Foundation, Inc., 59 Temple Place, Suite 330, 
 * Boston, MA  02111-1307  USA
 * ----------------------------------------------------- */

import java.io.*;
import java.net.*;
import java.security.NoSuchAlgorithmException;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.*;
import java.util.zip.GZIPInputStream;

import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.ParserConfigurationException;

import org.apache.commons.httpclient.*;
import org.apache.commons.httpclient.URI;
import org.apache.commons.httpclient.methods.GetMethod;
import org.apache.log4j.Logger;
import org.apache.log4j.Priority;
import org.methodize.nntprss.feed.db.ChannelDAO;
import org.methodize.nntprss.feed.parser.*;
import org.methodize.nntprss.util.AppConstants;
import org.methodize.nntprss.util.HttpUserException;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.xml.sax.SAXException;
import org.xml.sax.SAXParseException;

/**
 * @author Jason Brome <jason@methodize.org>
 * @version $Id: Channel.java,v 1.19 2007/12/17 04:08:39 jasonbrome Exp $
 */
public class Channel extends ItemContainer implements Runnable {

    public static final int EXTERNAL_VERSION = 2;

    public static final int STATUS_OK = 0;
    public static final int STATUS_NOT_FOUND = 1;
    public static final int STATUS_INVALID_CONTENT = 2;
    public static final int STATUS_CONNECTION_TIMEOUT = 3;
    public static final int STATUS_UNKNOWN_HOST = 4;
    public static final int STATUS_NO_ROUTE_TO_HOST = 5;
    public static final int STATUS_SOCKET_EXCEPTION = 6;
    public static final int STATUS_PROXY_AUTHENTICATION_REQUIRED = 7;
    public static final int STATUS_USER_AUTHENTICATION_REQUIRED = 8;

    public static final long EXPIRATION_KEEP = -1;

    private static final int PUSHBACK_BUFFER_SIZE = 4;

    private static final Logger log = Logger.getLogger(Channel.class);

    private String author;
    private URL url;
    private int id;
    private String title;
    private String link;
    private String description;
    private Date lastPolled;
    private Date lastCleaned;
    private long lastModified;
    private String lastETag;
    private String rssVersion;
    private String managingEditor;

    //   private boolean historical = true;
    private boolean enabled = true;
    private boolean parseAtAllCost = false;

    private long expiration = EXPIRATION_KEEP;

    // Publishing related
    private boolean postingEnabled = false;
    private String publishAPI = null;
    private Map publishConfig = null;

    private int status = STATUS_OK;
    private long pollingIntervalSeconds = DEFAULT_POLLING_INTERVAL;

    private ChannelManager channelManager;
    private ChannelDAO channelDAO;
    private Category category = null;

    private transient boolean polling = false;
    private transient boolean connected = false;

    public static final long DEFAULT_POLLING_INTERVAL = 0;
    // Clean channels every 24 hours
    public static final long CLEANING_INTERVAL = 1000 * 60 * 60 * 24;
    private static final int HTTP_CONNECTION_TIMEOUT = 1000 * 60 * 5;

    //   private HttpURLConnection httpCon = null;

    private HttpClient httpClient = null;
    private SimpleDateFormat httpDate = new SimpleDateFormat("EEE, dd MMM yyyy HH:mm:ss z", Locale.US);

    // Channel Doc Parsers
    private static GenericParser[] parsers = new GenericParser[] { RSSParser.getParser(), AtomParser.getParser() };

    public Channel() {
    }

    public Channel(String name, String urlString) throws MalformedURLException {
        this.name = name;
        this.url = new URL(urlString);
        initialize();
    }

    private void initialize() {
        channelManager = ChannelManager.getChannelManager();
        channelDAO = channelManager.getChannelDAO();
        //      httpClient = channelManager.getHttpClient();
        //      httpClient = new HttpClient();

        httpClient = new HttpClient(channelManager.getHttpConMgr());
        httpClient.setConnectionTimeout(HTTP_CONNECTION_TIMEOUT);
        httpClient.setTimeout(HTTP_CONNECTION_TIMEOUT);

        // Initialize user id / password for protected feeds
        if (url.getUserInfo() != null) {
            httpClient.getState().setCredentials(null, null,
                    new UsernamePasswordCredentials(URLDecoder.decode(url.getUserInfo())));
        }

        TimeZone gmt = TimeZone.getTimeZone("GMT");
        httpDate.setTimeZone(gmt);
    }

    /**
     * Returns the url.
     * @return String
     */
    public String getUrl() {
        return url.toString();
    }

    private HttpClient getHttpClient() {
        HttpClient httpClient = new HttpClient();
        httpClient.setConnectionTimeout(HTTP_CONNECTION_TIMEOUT);
        httpClient.setTimeout(HTTP_CONNECTION_TIMEOUT);

        // Initialize user id / password for protected feeds
        if (url.getUserInfo() != null) {
            httpClient.getState().setCredentials(null, null,
                    new UsernamePasswordCredentials(URLDecoder.decode(url.getUserInfo())));
        }
        return httpClient;

    }

    /**
     * Retrieves the latest RSS doc from the remote site
     */
    public synchronized void poll() {
        // Use method-level variable
        // Guard against change in history mid-poll
        polling = true;

        //      boolean keepHistory = historical;
        long keepExpiration = expiration;

        lastPolled = new Date();

        int statusCode = -1;
        HttpMethod method = null;
        String urlString = url.toString();
        try {
            HttpClient httpClient = getHttpClient();
            channelManager.configureHttpClient(httpClient);
            HttpResult result = null;

            try {

                connected = true;
                boolean redirected = false;
                int count = 0;
                do {
                    URL currentUrl = new URL(urlString);
                    method = new GetMethod(urlString);
                    method.setRequestHeader("User-agent", AppConstants.getUserAgent());
                    method.setRequestHeader("Accept-Encoding", "gzip");
                    method.setFollowRedirects(false);
                    method.setDoAuthentication(true);

                    // ETag
                    if (lastETag != null) {
                        method.setRequestHeader("If-None-Match", lastETag);
                    }

                    // Last Modified
                    if (lastModified != 0) {
                        final String NAME = "If-Modified-Since";
                        //defend against such fun like net.freeroller.rickard got If-Modified-Since "Thu, 24 Aug 2028 12:29:54 GMT"
                        if (lastModified < System.currentTimeMillis()) {
                            final String DATE = httpDate.format(new Date(lastModified));
                            method.setRequestHeader(NAME, DATE);
                            log.debug("channel " + this.name + " using " + NAME + " " + DATE); //ALEK
                        }
                    }

                    method.setFollowRedirects(false);
                    method.setDoAuthentication(true);

                    HostConfiguration hostConfig = new HostConfiguration();
                    hostConfig.setHost(currentUrl.getHost(), currentUrl.getPort(), currentUrl.getProtocol());

                    result = executeHttpRequest(httpClient, hostConfig, method);
                    statusCode = result.getStatusCode();
                    if (statusCode == HttpStatus.SC_MOVED_PERMANENTLY
                            || statusCode == HttpStatus.SC_MOVED_TEMPORARILY
                            || statusCode == HttpStatus.SC_SEE_OTHER
                            || statusCode == HttpStatus.SC_TEMPORARY_REDIRECT) {

                        redirected = true;
                        // Resolve against current URI - may be a relative URI
                        try {
                            urlString = new java.net.URI(urlString).resolve(result.getLocation()).toString();
                        } catch (URISyntaxException use) {
                            // Fall back to just using location from result
                            urlString = result.getLocation();
                        }
                        if (statusCode == HttpStatus.SC_MOVED_PERMANENTLY && channelManager.isObserveHttp301()) {
                            try {
                                url = new URL(urlString);
                                if (log.isInfoEnabled()) {
                                    log.info("Channel = " + this.name
                                            + ", updated URL from HTTP Permanent Redirect");
                                }
                            } catch (MalformedURLException mue) {
                                // Ignore URL permanent redirect for now...                        
                            }
                        }
                    } else {
                        redirected = false;
                    }

                    //               method.getResponseBody();
                    //               method.releaseConnection();
                    count++;
                } while (count < 5 && redirected);

            } catch (HttpRecoverableException hre) {
                if (log.isDebugEnabled()) {
                    log.debug("Channel=" + name + " - Temporary Http Problem - " + hre.getMessage());
                }
                status = STATUS_CONNECTION_TIMEOUT;
                statusCode = HttpStatus.SC_INTERNAL_SERVER_ERROR;
            } catch (ConnectException ce) {
                // @TODO Might also be a connection refused - not only a timeout...
                if (log.isDebugEnabled()) {
                    log.debug("Channel=" + name + " - Connection Timeout, skipping - " + ce.getMessage());
                }
                status = STATUS_CONNECTION_TIMEOUT;
                statusCode = HttpStatus.SC_INTERNAL_SERVER_ERROR;
            } catch (UnknownHostException ue) {
                if (log.isDebugEnabled()) {
                    log.debug("Channel=" + name + " - Unknown Host Exception, skipping");
                }
                status = STATUS_UNKNOWN_HOST;
                statusCode = HttpStatus.SC_INTERNAL_SERVER_ERROR;
            } catch (NoRouteToHostException re) {
                if (log.isDebugEnabled()) {
                    log.debug("Channel=" + name + " - No Route To Host Exception, skipping");
                }
                status = STATUS_NO_ROUTE_TO_HOST;
                statusCode = HttpStatus.SC_INTERNAL_SERVER_ERROR;
            } catch (SocketException se) {
                // e.g. Network is unreachable            
                if (log.isDebugEnabled()) {
                    log.debug("Channel=" + name + " - Socket Exception, skipping");
                }
                status = STATUS_SOCKET_EXCEPTION;
                statusCode = HttpStatus.SC_INTERNAL_SERVER_ERROR;
            }

            // Only process if ok - if not ok (e.g. not modified), don't do anything
            if (connected && statusCode == HttpStatus.SC_OK) {

                PushbackInputStream pbis = new PushbackInputStream(new ByteArrayInputStream(result.getResponse()),
                        PUSHBACK_BUFFER_SIZE);
                skipBOM(pbis);
                BufferedInputStream bis = new BufferedInputStream(pbis);
                DocumentBuilder db = AppConstants.newDocumentBuilder();

                try {
                    Document rssDoc = null;
                    if (!parseAtAllCost) {
                        try {
                            rssDoc = db.parse(bis);
                        } catch (InternalError ie) {
                            // Crimson library throws InternalErrors
                            if (log.isDebugEnabled()) {
                                log.debug("InternalError thrown by Crimson", ie);
                            }
                            throw new SAXException("InternalError thrown by Crimson: " + ie.getMessage());
                        }
                    } else {
                        // Parse-at-all-costs selected
                        // Read in document to local array - may need to parse twice
                        ByteArrayOutputStream bos = new ByteArrayOutputStream();
                        byte[] buf = new byte[1024];
                        int bytesRead = bis.read(buf);
                        while (bytesRead > -1) {
                            if (bytesRead > 0) {
                                bos.write(buf, 0, bytesRead);
                            }
                            bytesRead = bis.read(buf);
                        }
                        bos.flush();
                        bos.close();

                        byte[] rssDocBytes = bos.toByteArray();

                        try {
                            // Try the XML document parser first - just in case
                            // the doc is well-formed
                            rssDoc = db.parse(new ByteArrayInputStream(rssDocBytes));
                        } catch (SAXParseException spe) {
                            if (log.isDebugEnabled()) {
                                log.debug("XML parse failed, trying tidy");
                            }
                            // Fallback to parse-at-all-costs parser
                            rssDoc = LooseParser.parse(new ByteArrayInputStream(rssDocBytes));
                        }
                    }

                    processChannelDocument(expiration, rssDoc);

                    // Update last modified / etag from headers
                    //               lastETag = httpCon.getHeaderField("ETag");
                    //               lastModified = httpCon.getHeaderFieldDate("Last-Modified", 0);

                    Header hdrETag = method.getResponseHeader("ETag");
                    lastETag = hdrETag != null ? hdrETag.getValue() : null;

                    Header hdrLastModified = method.getResponseHeader("Last-Modified");
                    lastModified = hdrLastModified != null ? parseHttpDate(hdrLastModified.getValue()) : 0;
                    log.debug("channel " + this.name + " parsed Last-Modifed " + hdrLastModified + " to "
                            + (lastModified != 0 ? "" + (new Date(lastModified)) : "" + lastModified)); //ALEK

                    status = STATUS_OK;
                } catch (SAXParseException spe) {
                    if (log.isEnabledFor(Priority.WARN)) {
                        log.warn("Channel=" + name + " - Error parsing RSS document - check feed");
                    }
                    status = STATUS_INVALID_CONTENT;
                }

                bis.close();

                // end if response code == HTTP_OK
            } else if (connected && statusCode == HttpStatus.SC_NOT_MODIFIED) {
                if (log.isDebugEnabled()) {
                    log.debug("Channel=" + name + " - HTTP_NOT_MODIFIED, skipping");
                }
                status = STATUS_OK;
            } else if (statusCode == HttpStatus.SC_PROXY_AUTHENTICATION_REQUIRED) {
                if (log.isEnabledFor(Priority.WARN)) {
                    log.warn("Channel=" + name + " - Proxy authentication required");
                }
                status = STATUS_PROXY_AUTHENTICATION_REQUIRED;
            } else if (statusCode == HttpStatus.SC_UNAUTHORIZED) {
                if (log.isEnabledFor(Priority.WARN)) {
                    log.warn("Channel=" + name + " - Authentication required");
                }
                status = STATUS_USER_AUTHENTICATION_REQUIRED;
            }

            // Update channel in database...
            channelDAO.updateChannel(this);

        } catch (FileNotFoundException fnfe) {
            if (log.isEnabledFor(Priority.WARN)) {
                log.warn("Channel=" + name + " - File not found returned by web server - check feed");
            }
            status = STATUS_NOT_FOUND;
        } catch (Exception e) {
            if (log.isEnabledFor(Priority.WARN)) {
                log.warn("Channel=" + name + " - Exception while polling channel", e);
            }
        } catch (NoClassDefFoundError ncdf) {
            // Throw if SSL / redirection to HTTPS
            if (log.isEnabledFor(Priority.WARN)) {
                log.warn("Channel=" + name + " - NoClassDefFound", ncdf);
            }
        } finally {
            connected = false;
            polling = false;
        }

    }

    private void processChannelDocument(long keepExpiration, Document rssDoc)
            throws NoSuchAlgorithmException, IOException {
        Element rootElm = rssDoc.getDocumentElement();

        GenericParser docParser = null;
        for (int i = 0; i < parsers.length; i++) {
            if (parsers[i].isParsable(rootElm)) {
                docParser = parsers[i];
                break;
            }
        }

        if (docParser != null) {
            rssVersion = docParser.getFormatVersion(rootElm);
            docParser.extractFeedInfo(rootElm, this);
            docParser.processFeedItems(rootElm, this, channelDAO, keepExpiration < 0);
            channelDAO.updateChannel(this);
        } // end if docParser != null

    }

    private long parseHttpDate(String dateString) {
        long time = 0;
        try {
            time = httpDate.parse(dateString).getTime();
        } catch (ParseException pe) {
            // Ignore date if parse error
        }
        return time;
    }

    /**
     * Simple channel validation - ensures URL
     * is valid, XML document is returned, and
     * document has an rss root element with a 
     * version, or rdf root element, 
     */
    public static boolean isValid(URL url) throws HttpUserException {
        boolean valid = false;
        try {
            //         System.setProperty("networkaddress.cache.ttl", "0");

            HttpClient client = new HttpClient();
            ChannelManager.getChannelManager().configureHttpClient(client);
            if (url.getUserInfo() != null) {
                client.getState().setCredentials(null, null,
                        new UsernamePasswordCredentials(URLDecoder.decode(url.getUserInfo())));
            }

            String urlString = url.toString();
            HttpMethod method = null;

            int count = 0;
            HttpResult result = null;
            int statusCode = HttpStatus.SC_OK;
            boolean redirected = false;
            do {
                method = new GetMethod(urlString);
                method.setRequestHeader("User-agent", AppConstants.getUserAgent());
                method.setRequestHeader("Accept-Encoding", "gzip");
                method.setFollowRedirects(false);
                method.setDoAuthentication(true);

                HostConfiguration hostConfiguration = client.getHostConfiguration();
                URI hostURI = new URI(urlString);
                hostConfiguration.setHost(hostURI);

                result = executeHttpRequest(client, hostConfiguration, method);
                statusCode = result.getStatusCode();
                if (statusCode == HttpStatus.SC_MOVED_PERMANENTLY || statusCode == HttpStatus.SC_MOVED_TEMPORARILY
                        || statusCode == HttpStatus.SC_SEE_OTHER
                        || statusCode == HttpStatus.SC_TEMPORARY_REDIRECT) {
                    redirected = true;
                    // Resolve against current URI - may be a relative URI
                    try {
                        urlString = new java.net.URI(urlString).resolve(result.getLocation()).toString();
                    } catch (URISyntaxException use) {
                        // Fall back to just using location from result
                        urlString = result.getLocation();
                    }
                } else {
                    redirected = false;
                }

                //            method.getResponseBody();
                //            method.releaseConnection();
                count++;
            } while (count < 5 && redirected);

            // Only process if ok - if not ok (e.g. not modified), don't do anything
            if (statusCode == HttpStatus.SC_OK) {
                PushbackInputStream pbis = new PushbackInputStream(new ByteArrayInputStream(result.getResponse()),
                        PUSHBACK_BUFFER_SIZE);
                skipBOM(pbis);

                BufferedInputStream bis = new BufferedInputStream(pbis);
                DocumentBuilder db = AppConstants.newDocumentBuilder();
                Document rssDoc = db.parse(bis);
                Element rootElm = rssDoc.getDocumentElement();

                for (int i = 0; i < parsers.length; i++) {
                    if (parsers[i].isParsable(rootElm)) {
                        valid = true;
                        break;
                    }
                }
            } else if (statusCode == HttpStatus.SC_PROXY_AUTHENTICATION_REQUIRED) {
                throw new HttpUserException(statusCode);
            } else if (statusCode == HttpStatus.SC_UNAUTHORIZED) {
                throw new HttpUserException(statusCode);
            }

        } catch (URIException e) {
            e.printStackTrace();
        } catch (ParserConfigurationException e) {
            e.printStackTrace();
        } catch (SAXException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }

        return valid;
    }

    private static void skipBOM(PushbackInputStream is) throws IOException {
        byte[] header = new byte[PUSHBACK_BUFFER_SIZE];
        int bytesRead = is.read(header);
        if (header[0] == 0 && header[1] == 0 && (header[2] & 0xff) == 0xFE && (header[3] & 0xff) == 0xFF) {
            // UTF-32, big-endian
        } else if ((header[0] & 0xff) == 0xFF && (header[1] & 0xff) == 0xFE && header[2] == 0 && header[3] == 0) {
            // UTF-32, little-endian
        } else if ((header[0] & 0xff) == 0xFE && (header[1] & 0xff) == 0xFF) {
            is.unread(header, 2, 2);
            // UTF-16, big-endian
        } else if ((header[0] & 0xff) == 0xFF && (header[1] & 0xff) == 0xFE) {
            is.unread(header, 2, 2);
            // UTF-16, little-endian
        } else if ((header[0] & 0xff) == 0xEF && (header[1] & 0xff) == 0xBB && (header[2] & 0xff) == 0xBF) {
            // UTF-8
            is.unread(header, 3, 1);
        } else {
            is.unread(header, 0, PUSHBACK_BUFFER_SIZE);
        }
    }

    public void save() {
        // Update channel in database...
        channelDAO.updateChannel(this);
    }

    /**
     * Validates the channel
     */
    public boolean isValid() throws HttpUserException {
        return isValid(url);
    }

    /**
     * Returns the author.
     * @return String
     */
    public String getAuthor() {
        return author;
    }

    /**
     * Sets the author.
     * @param author The author to set
     */
    public void setAuthor(String author) {
        this.author = author;
    }

    /**
     * Returns the id.
     * @return int
     */
    public int getId() {
        return id;
    }

    /**
     * Sets the id.
     * @param id The id to set
     */
    public void setId(int id) {
        this.id = id;
    }

    /**
     * Returns the lastPolled.
     * @return Date
     */
    public Date getLastPolled() {
        return lastPolled;
    }

    /**
     * Sets the lastPolled.
     * @param lastPolled The lastPolled to set
     */
    public void setLastPolled(Date lastPolled) {
        this.lastPolled = lastPolled;
    }

    public boolean isAwaitingPoll() {
        // Need intelligent algorithm to handle this...
        // Currently just poll once an hour
        boolean awaitingPoll = false;

        if (lastPolled != null) {
            long currentTimeMillis = System.currentTimeMillis();

            long pollingInterval = this.pollingIntervalSeconds;
            if (pollingInterval == DEFAULT_POLLING_INTERVAL) {
                pollingInterval = channelManager.getPollingIntervalSeconds();
            }

            if ((currentTimeMillis - lastPolled.getTime()) > (pollingInterval * 1000)) {
                awaitingPoll = true;
            } else if (lastPolled.getTime() > currentTimeMillis) {
                // Sanity date check - if the last polling time is greater than the
                // current time, assume that there was an issue with the system clock,
                // and repoll
                awaitingPoll = true;
            }

        } else {
            awaitingPoll = true;
        }

        return awaitingPoll;
    }

    /**
     * @see java.lang.Runnable#run()
     */
    public void run() {
        if (log.isInfoEnabled()) {
            log.info("Polling channel " + name);
        }

        if (!polling)
            poll();

        if (log.isInfoEnabled()) {
            log.info("Finished polling channel " + name);
        }

    }

    /**
     * Returns the lastETag.
     * @return String
     */
    public String getLastETag() {
        return lastETag;
    }

    /**
     * Returns the lastModified.
     * @return long
     */
    public long getLastModified() {
        return lastModified;
    }

    /**
     * Sets the lastETag.
     * @param lastETag The lastETag to set
     */
    public void setLastETag(String lastETag) {
        this.lastETag = lastETag;
    }

    /**
     * Sets the lastModified.
     * @param lastModified The lastModified to set
     */
    public void setLastModified(long lastModified) {
        this.lastModified = lastModified;
    }

    /**
     * Returns the rssVersion.
     * @return String
     */
    public String getRssVersion() {
        return rssVersion;
    }

    /**
     * Sets the rssVersion.
     * @param rssVersion The rssVersion to set
     */
    public void setRssVersion(String rssVersion) {
        this.rssVersion = rssVersion;
    }

    /**
     * Sets the url.
     * @param url The url to set
     */
    public void setUrl(URL url) {
        if (this.url == null || !this.url.equals(url)) {
            this.url = url;

            // If we change the URL, then reset the 
            // polling characteristics associated with the channel
            this.lastModified = 0;
            this.lastETag = null;
            this.lastPolled = null;
        }

    }

    /**
     * Returns the historical.
     * @return boolean
     */
    //   public boolean isHistorical() {
    //      return historical;
    //   }

    /**
     * Sets the historical.
     * @param historical The historical to set
     */
    //   public void setHistorical(boolean historical) {
    //      this.historical = historical;
    //   }

    /**
     * Returns the status.
     * @return int
     */
    public int getStatus() {
        return status;
    }

    /**
     * Sets the status.
     * @param status The status to set
     */
    public void setStatus(int status) {
        this.status = status;
    }

    /**
     * Returns the description.
     * @return String
     */
    public String getDescription() {
        return description;
    }

    /**
     * Returns the link.
     * @return String
     */
    public String getLink() {
        return link;
    }

    /**
     * Returns the title.
     * @return String
     */
    public String getTitle() {
        return title;
    }

    /**
     * Sets the description.
     * @param description The description to set
     */
    public void setDescription(String description) {
        this.description = description;
    }

    /**
     * Sets the link.
     * @param link The link to set
     */
    public void setLink(String link) {
        this.link = link;
    }

    /**
     * Sets the title.
     * @param title The title to set
     */
    public void setTitle(String title) {
        this.title = title;
    }

    /**
     * Returns the enabled.
     * @return boolean
     */
    public boolean isEnabled() {
        return enabled;
    }

    /**
     * Sets the enabled.
     * @param enabled The enabled to set
     */
    public void setEnabled(boolean enabled) {
        this.enabled = enabled;
    }

    /**
     * Returns the managingEditor.
     * @return String
     */
    public String getManagingEditor() {
        return managingEditor;
    }

    /**
     * Sets the managingEditor.
     * @param managingEditor The managingEditor to set
     */
    public void setManagingEditor(String managingEditor) {
        this.managingEditor = managingEditor;
    }

    /**
     * Returns the postingEnabled.
     * @return boolean
     */
    public boolean isPostingEnabled() {
        return postingEnabled;
    }

    /**
     * Sets the postingEnabled.
     * @param postingEnabled The postingEnabled to set
     */
    public void setPostingEnabled(boolean postingEnabled) {
        this.postingEnabled = postingEnabled;
    }

    /**
     * Returns the parseAtAllCost.
     * @return boolean
     */
    public boolean isParseAtAllCost() {
        return parseAtAllCost;
    }

    /**
     * Sets the parseAtAllCost.
     * @param parseAtAllCost The parseAtAllCost to set
     */
    public void setParseAtAllCost(boolean parseAtAllCost) {
        this.parseAtAllCost = parseAtAllCost;
    }

    /**
     * Returns the publishAPI.
     * @return String
     */
    public String getPublishAPI() {
        return publishAPI;
    }

    /**
     * Sets the publishAPI.
     * @param publishAPI The publishAPI to set
     */
    public void setPublishAPI(String publishAPI) {
        this.publishAPI = publishAPI;
    }

    /**
     * Returns the polling.
     * @return boolean
     */
    public boolean isPolling() {
        return polling;
    }

    /**
     * Returns the publishConfig.
     * @return Map
     */
    public Map getPublishConfig() {
        return publishConfig;
    }

    /**
     * Sets the publishConfig.
     * @param publishConfig The publishConfig to set
     */
    public void setPublishConfig(Map publishConfig) {
        this.publishConfig = publishConfig;
    }

    /**
     * Returns the pollingIntervalSeconds.
     * @return long
     */
    public long getPollingIntervalSeconds() {
        return pollingIntervalSeconds;
    }

    /**
     * Sets the pollingIntervalSeconds.
     * @param pollingIntervalSeconds The pollingIntervalSeconds to set
     */
    public void setPollingIntervalSeconds(long pollingIntervalSeconds) {
        this.pollingIntervalSeconds = pollingIntervalSeconds;
    }

    /**
     * Executes HTTP request
     * @param client
     * @param config
     * @param method
     */

    private static HttpResult executeHttpRequest(HttpClient client, HostConfiguration config, HttpMethod method)
            throws HttpException, IOException {

        HttpResult result;
        int statusCode = -1;

        try {
            statusCode = client.executeMethod(config, method);

            //      while (statusCode == -1 && attempt < 3) {
            //         try {
            //            // execute the method.
            //            statusCode = client.executeMethod(config, method);
            //         } catch (HttpRecoverableException e) {
            //            log.(
            //               "A recoverable exception occurred, retrying."
            //                  + e.getMessage());
            //            method.releaseConnection();
            //            method.recycle();
            //            try {
            //               Thread.sleep(250);
            //            } catch(InterruptedException ie) {
            //            }
            //         } 
            //      }

            result = new HttpResult(statusCode);
            Header locationHeader = method.getResponseHeader("location");
            if (locationHeader != null) {
                result.setLocation(locationHeader.getValue());
            }

            if (statusCode == HttpStatus.SC_OK) {
                Header contentEncoding = method.getResponseHeader("Content-Encoding");
                if (contentEncoding != null && contentEncoding.getValue().equals("gzip")) {
                    InputStream is = method.getResponseBodyAsStream();
                    is = new GZIPInputStream(is);
                    ByteArrayOutputStream baos = new ByteArrayOutputStream();
                    byte[] buf = new byte[1024];
                    int bytesRead;
                    while ((bytesRead = is.read(buf)) > -1) {
                        if (bytesRead > 0) {
                            baos.write(buf, 0, bytesRead);
                        }
                    }
                    baos.flush();
                    baos.close();
                    is.close();
                    result.setResponse(baos.toByteArray());
                } else {
                    result.setResponse(method.getResponseBody());
                }
            } else {
                // Process response
                InputStream is = method.getResponseBodyAsStream();
                if (is != null) {
                    byte[] buf = new byte[1024];
                    while (is.read(buf) != -1)
                        ;
                    is.close();
                }
                //   result.setResponse(method.getResponseBody());      
            }

            return result;
        } finally {
            method.releaseConnection();
        }
    }

    private static class HttpResult {

        private int statusCode;
        private byte[] response;
        private String location;

        public HttpResult(int statusCode) {
            this.statusCode = statusCode;
        }

        public int getStatusCode() {
            return statusCode;
        }

        public byte[] getResponse() {
            return response;
        }

        public void setResponse(byte[] response) {
            this.response = response;
        }

        public String getLocation() {
            return location;
        }

        public void setLocation(String location) {
            this.location = location;
        }
    }

    /**
     * @return
     */
    public long getExpiration() {
        return expiration;
    }

    /**
     * @param l
     */
    public void setExpiration(long l) {
        expiration = l;
    }

    /**
     * @return
     */
    public Category getCategory() {
        return category;
    }

    /**
     * @param category
     */
    public void setCategory(Category category) {
        this.category = category;
    }

    /**
     * @return
     */
    public Date getLastCleaned() {
        return lastCleaned;
    }

    /**
     * @param date
     */
    public void setLastCleaned(Date date) {
        lastCleaned = date;
    }

    public String toString() {
        return "{Channel" + " author=" + author + " url=" + url + " id=" + id + " title=" + title + " link=" + link
                + " description=" + description + " lastPolled=" + lastPolled + " lastCleaned=" + lastCleaned
                + " lastModified=" + lastModified + " (" + new Date(lastModified) + ")" + " lastETag=" + lastETag
                + " rssVersion=" + rssVersion + " managingEditor=" + managingEditor + " lastArticleNumber="
                + lastArticleNumber + " expiration=" + expiration + " enabled=" + enabled + " parseAtAllCost="
                + parseAtAllCost
                //+" publishConfig
                + "}";
    }
}