Skip to content

Instantly share code, notes, and snippets.

@FauxFaux
Forked from theresajayne/gist:1064527
Created July 5, 2011 09:23
Show Gist options
  • Save FauxFaux/1064544 to your computer and use it in GitHub Desktop.
Save FauxFaux/1064544 to your computer and use it in GitHub Desktop.
package ptv.publishing;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.URISyntaxException;
import java.sql.Clob;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.text.Format;
import java.text.MessageFormat;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.Date;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Random;
import java.util.Set;
import java.util.concurrent.ExecutorService;
import java.util.regex.Pattern;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.FactoryConfigurationError;
import javax.xml.parsers.ParserConfigurationException;
import org.apache.commons.lang.time.FastDateFormat;
import org.apache.log4j.Level;
import org.apache.log4j.Logger;
import org.apache.lucene.document.Field;
import org.apache.xpath.XPathAPI;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import ptv.cmd.FileFlusher;
import ptv.common.Constants;
import ptv.common.PTVHelper;
import ptv.db.ConnectionPool;
import ptv.encryption.Rot13;
import ptv.matchlive.Player;
import ptv.matchlive.Team;
import ptv.motorsport.rally.stats.Driver;
import ptv.motorsport.rally.stats.Rally;
import ptv.motorsport.rally.stats.RallyTeam;
import ptv.page.ArticleDetail;
import ptv.page.ArticleIndex;
import ptv.page.CustomArticleIndex;
import ptv.page.CustomLinkedArticleDetail;
import ptv.page.HtmlCache;
import ptv.page.LiveMatchContent;
import ptv.page.MulticategoryArticleIndex;
import ptv.page.Page;
import ptv.page.PageElement;
import ptv.page.TabbedVideoArticleIndex;
import ptv.publishing.detailobject.BrightcoveVideo;
import ptv.publishing.detailobject.DetailObjectListable;
import ptv.publishing.detailobject.MavenVideo;
import ptv.publishing.detailobject.SimpleArticleLink;
import ptv.publishing.detailobject.listingstrategy.DateCategoryListingStrategy;
import ptv.publishing.detailobject.request.DateCategoryListingRequest;
import ptv.publishing.util.KeywordLinksUtility;
import ptv.publishing.util.KeywordLinksUtilityImpl;
import ptv.rating.Rating;
import ptv.request.ServiceRequest;
import ptv.rss.RssController;
import ptv.search.lucene.LuceneIndexFieldsUtils;
import ptv.search.lucene.LuceneIndexable;
import ptv.search.lucene.LuceneUtils;
import ptv.seo.SEOFriendlyURLEnabled;
import ptv.seo.SEOURLsUtils;
import ptv.sitemap.NewsSiteMapEntry;
import ptv.sitemap.SiteMap;
import ptv.sitemap.SiteMapType;
import ptv.sitemap.SiteMapUtils;
import ptv.stats.Competition;
import ptv.stats.Match;
import ptv.stats.Site;
import ptv.streaming.EventDetailObject;
import ptv.syndication.EditorialSyndicator;
import ptv.syndication.HTMLProcessor;
import ptv.tracking.TrackingAsset;
import ptv.tracking.TrackingCategory;
import ptv.url.ExternalUrl;
import ptv.url.FlashStreamURL;
import ptv.usercontent.Comment;
import ptv.usercontent.CommentGroup;
import ptv.utilities.ArrayUtils;
import ptv.utilities.CachedTypedProperties;
import ptv.utilities.Curl;
import ptv.utilities.DateTimeUtils;
import ptv.utilities.ErrorLog;
import ptv.utilities.HashKey;
import ptv.utilities.InvalidCurlException;
import ptv.utilities.ListUtils;
import ptv.utilities.OracleSQLFilter;
import ptv.utilities.StringUtils;
import ptv.utilities.ThreadPoolFactory;
import ptv.vdp.dto.EventDetail;
import ptv.vdp.dto.EventForPartnerBase;
import ptv.video.Clip;
import ptv.video.Video;
import ptv.video.VideoType;
import ptv.xmlHttpRequest.SvaCachingFilter;
import ptv.xmlHttpRequest.SvaXmlHttpServlet;
/**
* Article is the class that implements an article bean. It holds most
* information needed to encapsulate an article, including article Id,
* owner (the site that actually owns the article), the site that this instance
* of the article is being used by, and article parts.
*/
public class Article implements DetailObject, DetailObjectListable, CustomPageTitle,
NewsSiteMapEntry, LuceneIndexable, DetailObjectLoader<Article>,
SEOFriendlyURLEnabled {
private static final Logger logger = Logger.getLogger(Article.class);
private static final String ARTICLE_PROPERTIES_FILE_NAME = "article.properties";
/**
* Specifies to retrieve the Articles in ascending order for the
* page element.
*/
private static final int ARTICLES_FOR_MONTH_INDICATOR_ORD_ASC = 1001;
/**
* Property map which stores the properties of this class
*/
private static Map<String, Class> propertyMap = new HashMap<String, Class>();
/**
* This property represents the headline of the article object.
* The most commonly used return type class for this would be
* <code>java.lang.String</code>, but may wish to provide different kind.
*/
public static String HEADLINE = "headline";
/**
* This property represents the headline of the article object.
* The most commonly used return type class for this would be
* <code>java.lang.String</code>, but may wish to provide different kind.
*/
public static String TEASER = "teaser";
/**
* This property represents the headline of the article object.
* The most commonly used return type class for this would be
* <code>java.lang.String</code>, but may wish to provide different kind.
*/
public static String VIDEOHI = "videoHi";
/**
* This property represents the headline of the article object.
* The most commonly used return type class for this would be
* <code>java.lang.String</code>, but may wish to provide different kind.
*/
public static String VIDEOLO = "videoLo";
/**
* This property represents the headline of the article object.
* The most commonly used return type class for this would be
* <code>java.lang.String</code>, but may wish to provide different kind.
*/
public static String VIDEOMEDIUM = "videoMedium";
public static String HEADERIMAGEPATH = "headerImagePath";
public static String TEASERIMAGEPATH = "teaserImagePath";
/**
* Properties of this class which are exposed to outer world
*/
static {
propertyMap.put(TITLE, java.lang.String.class);
propertyMap.put(CURL, ptv.utilities.Curl.class);
propertyMap.put(THUMBNAIL, ptv.publishing.Image.class);
propertyMap.put(VIDEOHI, java.lang.String.class);
propertyMap.put(VIDEOLO, java.lang.String.class);
propertyMap.put(VIDEOMEDIUM, java.lang.String.class);
propertyMap.put(HEADERIMAGEPATH, java.lang.String.class);
propertyMap.put(TEASERIMAGEPATH, java.lang.String.class);
propertyMap.put(TEASER, java.lang.String.class);
propertyMap.put(DATE, java.util.Date.class);
propertyMap.put(CATEGORY, java.lang.Integer.class);
}
/**
* Name of logger for class. This instance of the logger is shared by all
* instances of the class.
*/
protected static final Logger LOGGER = Logger.getLogger(Article.class);
/**
* An enumerator that defines whether the articles needs to be
* excluded for comments or ratings
*/
public enum ExcludeArticlesFor { COMMENTS , RATINGS }
/**
* Specifies home page article ranking.
*/
public static final int HOME_PAGE_RANKING = 0;
/**
* Specifies article ranking by category.
*/
public static final int CATEGORY_RANKING = 1;
/**
* For desktop alerts parameter in getArticles - indicates that only articles
* for the current site that have the desktop alert flag set true are to be
* retrieved.
*/
public static final int ALERTS_FOR_THIS_SITE = 2;
/**
* For desktop alerts parameter in getArticles - indicates that articles for
* all site that have the desktop alert flag set true are to be retrieved.
*/
public static final int ALERTS_FOR_ALL_CLUBS = 3;
/**
* Specifies article ranking which allows article sharing across
* mutliple sites.
*/
public static final int ARTICLE_SHARING_RANKING = 2;
/**
* Rank code for home page ranks
*/
public static final String HOME_CATEGORY_RANK_TYPE = "HMPG";
/**
* Rank code for page ranks. The actual value is a holdover from the
* vignette legacy code.
*/
public static final String CATEGORY_RANK_TYPE = "TOP5";
/**
* Denotes the headline part of the article - used to restrict searching
* - see getArticlesContaining
*/
public static final String ARTICLE_PART_HEADLINE = "headline";
/**
* Denotes the teaser part of the article - used to restrict searching
* - see getArticlesContaining
*/
public static final String ARTICLE_PART_TEASER = "teaser";
/**
* Denotes the body part of the article - used to restrict searching
* - see getArticlesContaining
*/
public static final String ARTICLE_PART_BODY = "body";
/**
* Denotes the keywords part of the article - used to restrict searching
* - see getArticlesContaining
*/
public static final String ARTICLE_PART_KEYWORDS = "keywords";
/**
* Array containing all valid article parts
*/
public static final String[] ALL_ARTICLE_PARTS = {
ARTICLE_PART_HEADLINE,
ARTICLE_PART_TEASER,
ARTICLE_PART_BODY,
ARTICLE_PART_KEYWORDS
};
/**
* Set containing valid all valid article parts
*/
public static final HashSet<String> ALL_ARTICLE_PARTS_SET = new HashSet<String>();
static {
for (int ii = 0; ii < ALL_ARTICLE_PARTS.length; ++ii) {
ALL_ARTICLE_PARTS_SET.add(ALL_ARTICLE_PARTS[ii]);
}
}
/**
* Maximum number of articles that can be ranked.
*/
public static int MAXIMUM_RANKED_ARTICLES = 10;
/**
* A rank parameter is set to this value when the corresponding article is
* not ranked, ie doesn't appear in the database table ranking table.
*/
public static final int NOT_RANKED = -1;
/**
* Indicates that the rank information has not been read from the database
* for this article. Since rank information is stored in another table,
* retrieving this information is relatively expensive and is only required
* for a minority of articles (i.e. those being edited as opposed to those
* being displayed). As such article ranks are not guaranteed to be
* instantiated unless initialised using a particular method.
*/
private static final int RANK_NOT_INITIALISED = -2;
/**
* Constant indicating that the article has no id - i.e. this is a new
* article. No article has the id of 0, which means that initialisation
* as this value will not conflict with a particular existing article.
* Callers can test the results of getId() against Article.NEW_ARTICLE to
* determine whether an article has yet been saved to the database.
*/
public static final int NEW_ARTICLE = 0;
/**
* constant indicating that no image is associated with an article.
*/
public static final int NO_IMAGE_ID = 0;
/**
* Constant which holds columns selected from the articles table in a sql
* query retreiving article data from a database.
*/
protected static final String SQL_ARTICLE_COLUMNS_NO_ARTICLE_DATE =
"a.artl_id, " +
"a.headline, " +
"a.teaser, " +
"a.summary, " +
"a.body, " +
"a.orgn_club_id, " +
"a.site_posted_date, " +
"a.site_removed_date, " +
"a.catg_id, " +
"a.hmpg_flg, " +
"a.last_edit_date, " +
"a.header_image_id, " +
"a.teaser_image_id, " +
"a.mobile_image_id, " +
"a.general_flg, " +
"a.audio_stream, " +
"a.video_lo, " +
"a.video_medium, " +
"a.video_hi, " +
"a.video_download, " +
"a.syndicated_flg, " +
"a.mobile_site_flg, " +
"a.podcasting_flg, " +
"a.google_video_flg, " +
"a.google_video_date, " +
"a.flash_file, " +
"a.teaser_flash_file, " +
"a.flash_script, " +
"a.flash_teaser_script, " +
"a.desktop_alert_flg, " +
"a.syndication_timestamp, " +
"a.url, " +
"a.lock_id, " +
"a.lock_date, " +
"a.unsyndicated_date, " +
"a.video_holding_image_id, " +
"a.video_play_count, " +
"a.newsletter_flg, " +
"a.available_in_player_flg " ;
/**
* Constant which holds columns selected from the articles table in a sql
* query retrieving article data from a database.
*/
protected static final String SQL_ARTICLE_COLUMNS = "a.article_date, "
+ SQL_ARTICLE_COLUMNS_NO_ARTICLE_DATE;
/**
* SQL Fragament used to return live articles
*/
protected static final String LIVE_ARTICLES_SQL =
"AND SYSDATE >= a.article_date " +
"AND SYSDATE >= NVL(a.site_posted_date, " +
"TO_DATE('01-Jan-3001', 'DD-Mon-YYYY')) " +
"AND SYSDATE < NVL(a.site_removed_date, " +
"TO_DATE('01-Jan-3001', 'DD-Mon-YYYY')) ";
/**
* {@link MessageFormat} SQL fragment used to determine if videos are attached
* to an article.
*/
protected static final String FMT_SQL_VIDEOS_ATTACHED =
"AND (EXISTS(" +
"SELECT av.article_id " +
"FROM article_videos av " +
"WHERE av.article_id={0}artl_id) OR " +
"({0}video_hi IS NOT NULL) OR ({0}video_medium IS NOT NULL) OR " +
"({0}video_lo IS NOT NULL) OR ({0}video_download IS NOT NULL)) ";
/**
* {@link MessageFormat} SQL fragment used to determine if videos are NOT
* attached to an article.
*/
protected static final String FMT_SQL_VIDEOS_NOT_ATTACHED =
"AND (NOT EXISTS(" +
"SELECT av.article_id " +
"FROM article_videos av " +
"WHERE av.article_id={0}artl_id) AND " +
"({0}video_hi IS NULL) AND ({0}video_medium IS NULL) AND " +
"({0}video_lo IS NULL) AND ({0}video_download IS NULL)) ";
/**
* Query used by the listing strategy to determine which articles to retrieve
* when listing by the date the article was launched only.
*/
private static final String SELECT_FOR_LISTING_BY_POSTED_DATE = "SELECT " + Article.SQL_ARTICLE_COLUMNS +
"FROM editorial_articles a " +
"WHERE a.article_date >= ? " +
"AND a.article_date < ? " +
"AND a.orgn_club_id = ? " +
"AND SYSDATE >= a.article_date " +
"AND SYSDATE >= NVL(a.site_posted_date, TO_DATE('01-Jan-3001', 'DD-Mon-YYYY')) " +
"AND SYSDATE < NVL(a.site_removed_date, TO_DATE('01-Jan-3001', 'DD-Mon-YYYY')) " +
" AND a.catg_id = ? " +
" ORDER BY a.article_date DESC";
private static final String SELECT_FOR_LISTING_BY_LINKED_AND_POSTED_DATE_PREFIX_SQL = "SELECT CASE WHEN eut." + SimpleArticleLink.FIELD_VALUE + " IS NULL THEN a.article_date ELSE TO_DATE(eut." + SimpleArticleLink.FIELD_VALUE + ", 'Mon DD YYYY, HH24:MI') END AS article_date, " + Article.SQL_ARTICLE_COLUMNS_NO_ARTICLE_DATE +
"FROM editorial_articles a " +
"LEFT JOIN article_links al ON (a.ARTL_ID = al.artl_id AND al.detail_type_id = ?) " +
"LEFT JOIN " + SimpleArticleLink.TABLE_NAME + " eut ON (al.LINK_ID = " + SimpleArticleLink.FIELD_INSTANCE_ID + ") AND (eut." + SimpleArticleLink.FIELD_DETAIL_TYPE_ID + " = al.DETAIL_TYPE_ID) " +
"WHERE CASE WHEN eut." + SimpleArticleLink.FIELD_VALUE + " IS NULL THEN a.article_date ELSE TO_DATE(eut." + SimpleArticleLink.FIELD_VALUE + ", 'Mon DD YYYY, HH24:MI') END >= ? " +
"AND CASE WHEN eut." + SimpleArticleLink.FIELD_VALUE + " IS NULL THEN a.article_date ELSE TO_DATE(eut." + SimpleArticleLink.FIELD_VALUE + ", 'Mon DD YYYY, HH24:MI') END < ? " +
"AND (a.orgn_club_id = ? OR general_flg = 'Y') " +
"AND SYSDATE >= a.article_date " +
"AND SYSDATE >= NVL(a.site_posted_date, TO_DATE('01-Jan-3001', 'DD-Mon-YYYY')) " +
"AND SYSDATE < NVL(a.site_removed_date, TO_DATE('01-Jan-3001', 'DD-Mon-YYYY')) ";
/**
* Query used by the listing strategy to determine which articles to retrieve
* when listing by the date the article was launched or by the linked date.
*
* param1 = detail type id for date (from page element).
* param2 = start date
* param3 = end date
* param4 = club id
* param5 = category id
*
*/
private static final String SELECT_FOR_LISTING_BY_LINKED_AND_POSTED_DATE_SINGLE_CATEGORY = SELECT_FOR_LISTING_BY_LINKED_AND_POSTED_DATE_PREFIX_SQL +
"AND a.catg_id = ? ORDER BY article_date DESC";
/**
* A string format representing the query above.
*
* param1 = detail type id for date (from page element).
* param2 = start date
* param3 = end date
* param4 = club id
*
* It is expected that the string that replaces the %s is a string of ? that
* will subsequently be replaced as normal.
*/
private static final String FMT_SELECT_FOR_LISTING_BY_LINKED_AND_POSTED_DATE_MULTI_CATEGORY = SELECT_FOR_LISTING_BY_LINKED_AND_POSTED_DATE_PREFIX_SQL +
"AND a.catg_id IN ( %s ) ";
/**
* Constant used to indicate that a list of all articles is required.
**/
public static final int ALL_ARTICLES = -1;
/**
* Constant used to indicate that only ranked articles should be returned
* See getArticlesByRank.
**/
public static final int ONLY_RANKED_ARTICLES = -2;
/**
* Constant for videoInclusion in getArticlesByDetailType - indicates that
* articles only if they have video content
*/
public static final int WITH_VIDEOS = -2;
/**
* Constant for videoInclusion in getArticlesByDetailType - indicates that
* articles are to be returned only if they have no video content
*/
public static final int WITHOUT_VIDEOS = -3;
/**
* Pattern to match relative urls in html
*/
private static final Pattern RELATIVE_URL_PATTERN = Pattern.compile(
"((href|src)\\s*=\\s*(\"|\')?)(/|(\\w++(?!:)))",
Pattern.CASE_INSENSITIVE
);
/**
* Date format string for Article Orange Content XML filename
*/
protected static final String XML_FILENAME_DATETIME_FORMAT_STR =
"yyyyMMddhhmmss";
/**
* Date format singleton used for formatting dates according to
* XML_FILENAME_DATETIME_FORMAT.
*/
public static final Format XML_FILENAME_DATETIME_FORMAT =
FastDateFormat.getInstance(XML_FILENAME_DATETIME_FORMAT_STR);
/**
* File extension for Orange Content XML file associated with this article.
*/
protected static final String XML_FILE_EXTENSION = ".xml";
/**
* Default value for the lock_Id on the article
*/
public static final int NO_LOCK_ID = 0;
/**
* HashMap for caching the headline limit configuration.
* This hashMap keys are site IDs, and the values are hashmap
* themselves (the category ID of articles being the key,
* and the actual size limit the value).
*/
public static Map<Integer, HashMap<Integer, Integer>> headlineSizeLimits = new HashMap<Integer, HashMap<Integer, Integer>>();
/**
* HashMap for caching the teaser limit configuration.
* This hashMap keys are site IDs, and the values are hashmap
* themselves (the category ID of articles being the key,
* and the actual size limit the value).
*/
public static Map<Integer, HashMap<Integer, Integer>> teaserSizeLimits = new HashMap<Integer, HashMap<Integer, Integer>>();
/**
* Hashmap for syndication configuration
*/
public static Map<Integer, HashMap<Integer, Boolean>> autoSyndicationConfig = new HashMap<Integer, HashMap<Integer, Boolean>>();
/**
* Key for hashmap in case of site-wide configuration for
* headline size limits.
*/
public static int HASHMAP_KEY_ORGANISATION = 0;
/**
* Indicates if the size limit hashmaps have been initialised.
*/
public static boolean sizeLimitsInitialised = false;
/**
* Indicates whether auto syndication config has been initialised
*/
public static boolean syndicationConfigInitialised = false;
/**
* Date of Article.
*/
private java.util.Date articleDate = null;
/**
* Id of Article. Upon initialisation an article has its ID set to
* Article.NEW_ARTICLE.
*/
private int articleId = NEW_ARTICLE;
/**
* Category for this <code>Article</code>.
*/
private Category category;
/**
* It's necessary to recall what the previous category was when the category
* changes, in order to update the ranks correctly (ranks must be deleted
* where the article had the previous category, and inserted with the new
* category).
*/
private Category priorCategory = null;
/**
* Headline (title) of the article.
*/
private String headline;
/**
* Summary of the article.
*/
private String summary;
/**
* Teaser of the article.
*/
private String teaser;
/**
* Body of the article.
*/
private String body;
/**
* Flash script associated to the article.
*/
private String flashScript;
/**
* Flash teaser script associated to the article.
*/
private String flashTeaserScript;
/**
* URL where the full story behind this article can be located - this is
* used when the article links to an external source.
*/
private String url;
/**
* Indicates if the article is available to all sites or else a subset of
* the sites.
*/
private boolean generalArticle = false;
/**
* Indicates whether the article is a home page article. This is true when the
* article must appear on the home page. It is set to false by default.
*/
private boolean onHomePage = false;
/**
* Set true when the onHomePage boolean is changed via the setOnHomePage
* method. This is used to determine whether pages containing home page
* indexes should be flushed when the article is saved.
*/
private boolean homePageStatusChanged = false;
/**
* The site that wrote the article represented by this bean. (Only this site
* and PTV editors are allowed to edit the article.)
*/
private Site ownedBy;
/**
* Site that is using this bean: may be different from {@link #ownedBy} if
* this article is being displayed on another site's site (a site other than
* the site that wrote it).
*/
private Site usedBy;
/**
* Page on which the Article will display. Note that this Page related to
* the Site on which the Article displays, not the Site that created the
* Article.
*/
protected Page page = null;
/**
* The page that is used for displaying the article on the Denmark portion of the player site,
* or null if the page is not available in player.
*/
private Page playerPage = null;
/**
* File name for the high quality video associated with this article.
*/
private String videoHi = null;
/**
* File name for the low quality video associated with this article.
*/
private String videoLo = null;
/**
* File name for the medium quality video associated with this article.
*/
private String videoMedium = null;
/**
* File name for the download video associated with this article.
*/
private String videoDownload = null;
/**
* Number of times a video will be played in the media player
*/
private int videoPlayCount = 1;
/**
* A list of filenames for teaser videos
*/
private String[] teaserVideos = null;
/**
* Audio stream associated with this article.
*/
private String audioStream = null;
/**
* Determines whether this article is available for syndication.
*/
private boolean syndicated = false;
/**
* Determine whether this article will by picked up by the desktop alert
* application.
*/
private boolean availableForAlerts = false;
/**
* Determine whether this article will by picked up and displayed on the
* organisations mobile site.
*/
private boolean mobileArticle = false;
/**
* Determine whether or not the article should be visible on the "World"
* (newly renamed "Player") part of the site.
*
* This is done at the article level instead of using categories, because
* we need a cross-site way to determine if an article should be pulled
* in the World section of the site (mainly for Lucene results).
*
*/
private boolean availableInPlayer = false;
/**
* Determine whether this article will be podcasted.
*/
private boolean podcastArticle = false;
/**
* Determine whether this article will be sent to google video.
*/
private boolean googleVideoArticle = false;
/**
* The date that the article was processed and sent to google video
*/
private Date googleVideoProcessedDate = null;
/**
* The url of the flash file attached to this article
*/
private String flashFile = null;
/**
* The url of the teaser flash file attached to this article
*/
private String teaserFlashFile = null;
/**
* The url of the base for the flash file in this article
*/
private String flashBase = null;
/**
* The url of the base for the teaser flash file in this article
*/
private String teaserFlashBase = null;
/**
* Whether the flash file is a shockwave file or flash file
*/
private boolean shockwave = false;
/**
* Indicates whether the availableForAlert status has changed - used by the
* article save method to determine whether the alert xml fees need to be
* flushed.
*/
private boolean alertStatusChanged = false;
/**
* time when this article became sydicated.
*/
private java.util.Date syndicatedTimeStamp;
/**
* time when this article was recinded from sydication.
*/
private java.util.Date unSyndicatedTimeStamp;
/**
* Id of the lock held on this article by syndication processes
*/
private int lockId = Article.NO_LOCK_ID;
/**
* Launch Date of Article as a java.util.Date.
*/
private java.util.Date sitePostedDate;
/**
* Last edit Date of Article as a java.util.Date.
*/
private java.util.Date lastEditDate;
/**
* Boolean to determine whether the article's last edit date should be
* updated when the article is saved. This is necessary, since there exist
* circumstances when an article may be resaved, but the last edit date will
* should not be changed - when ranking an article for example. This is
* important, since the last edit date is used to determine whether
* syndicated articles are resent, so this field should only be updated when
* fields relevant to that are altered.
*/
protected boolean updateLastEditDate = false;
/**
* Boolean to determine whether an article that was syndicated has been
* unsyndicated.
*/
protected boolean unsyndicated = false;
/**
* Site removed date (expiry date) of Article as a java.util.Date.
*/
private java.util.Date siteRemovedDate;
/**
* Value of the page (/category) rank.
*/
private int pageRank = RANK_NOT_INITIALISED;
/**
* Value of the home page rank.
*/
private int homePageRank = RANK_NOT_INITIALISED;
/**
* Determines if the page rank has been changed.
*/
private boolean pageRankUpdated = false;
/**
* Determines if the home page rank has been changed.
*/
private boolean homePageRankUpdated = false;
/**
* The curl id is the numeric id that appears in the curl used to build
* a link to display this article (see getCurl). Depending on the page which
* displays the article, and whether or not a player/match/crew/team/rally is
* linked to the article, this id may be either an article id, a player id,
* a match id, a crew id, a team id or rally id.
*/
protected int curlId;
/**
* When generating the curl associated to this article, we need to pass
* the detail object for which the curl will be generated. Normally this
* object will be the article itself, but in certain situations it can
* be a match or another object linked to the article.
*/
private DetailObject detailObjectForCurl;
/**
* Article header image.
*/
private Image headerImage = null;
/**
* Images are not necessarily instantiated with the the article, but the image
* id is always retrieved to allow the image to be retrieved if need be.
*/
private int headerImageId;
/**
* Article teaser image.
*/
private Image teaserImage = null;
/**
* Images are not necessarily instantiated with the the article, but the image
* id is always retrieved to allow the image to be retrieved if need be.
*/
private int teaserImageId;
/**
* Article mobile image (specifically a 410 * 500 JPEG specifically for
* syndaication to mobile operators)
*/
private Image mobileImage = null;
/**
* Id of the image to display when media is not playing
* */
private int videoHoldingImageId;
/**
* image to display when media is not playing
*/
private Image videoHoldingImage = null;
/**
* Images are not necessarily instantiated with the the article, but
* the mobile image id is always retrieved to allow the image to be
* retrieved if need be.
*/
private int mobileImageId;
/**
* Linked images are stored in a separate table unlike the header and teaser
* images, since there may be a variable number of them. They're intended to
* be displayed on the article detail page, as an alternative to embedding the
* image links inside the html. This functionality was originally developed
* for the nobok site. Please note that because of the extra over head
* involved in setting these images up, they're only retrieved when a single
* article is returned using Article.getArticle and
* Site.isArticleImageLinksEnabled() == true;
*/
private Image[] linkedImages = null;
/**
*/
private Video[] linkedVideos = null;
/**
* Date when the order was locked by syndicator (normally null)
*/
private java.util.Date lockDate;
/**
* Boolean determining whether this article should be displayed on the
* site's newsletter
*/
private boolean displayOnNewsletters = false;
/**
* Keywords associated with this article. These are used by the
* getArticlesByKeywords method to return matching articles. Please note
* that the keywords must be explicitly instantiated by the getKeywords
* method.
*/
private String[] relatedArticleKeywords = null;
/**
* All objects that may be associated with an article are placed in this
* hash and accessed via a string key. This includes site specific objects
* such as players, matches drivers etc.
*/
private Map<String, ArrayList<DetailObject>> linkedObjects =
Collections.synchronizedMap(new HashMap<String, ArrayList<DetailObject>>());
/**
* A <code>CommentGroup</code> instance associated with this article.
* With this commentGroup, following information can be gathered:
* 1. Average rating of the article
* 2. Number of times the article is being commented
* 3. Number of times the article is being rated.
*/
private CommentGroup commentGroup = null;
/**
* The video which created and maintains this article
*/
private Video owningVideo;
/**
* Return the article headline as string
*/
public String toString() {
return this.headline;
}
/**
* Return custom page Title
*/
public String getPageTitle(String defaultTitle) {
return defaultTitle + this.headline;
}
/**
* @return the owningVideo
*/
public Video getOwningVideo() {
return owningVideo;
}
/**
* @param owningVideo the owningVideo to set
*/
public void setOwningVideo(Video owningVideo) {
this.owningVideo = owningVideo;
}
/**
* @return Whether this article is automatically generated
*/
public boolean isAutomaticallyGenerated() {
return null != getOwningVideo();
}
/**
* Return the linkedObjects hashmap
*
* @return linkedObjects HashMap
*/
public Map<String, ArrayList<DetailObject>> getLinkedObjects() {
return this.linkedObjects;
}
/**
* Factory method that returns a new article instance
*
* @param site The site the new article is going to belong to.
*
* @return A new article.
*/
public static Article getInstance(Site site) {
return new Article();
}
/**
* Ensure we have a default constructor to allow
* <code>ptv.publishing.DetailType</code> to instanciate article
* via reflection.
*/
public Article() {}
/**
* Retrieves the article date for this <code>Article</code>.
*
* @return The article Date.
*/
public java.util.Date getArticleDate() {
//
// .:TBC:. MM/MY This is potentially a bit misleading. We should probably
// just make callers responsible for handling articles that have no dates
// (usually because the caller didn't set one in a new article!).
//
if (null == this.articleDate) {
this.articleDate = new java.util.Date();
}
return this.articleDate;
}
/**
* Retrieves the article id.
*
* @return The article id.
*/
public int getArticleId() {
return this.articleId;
}
/**
* Retrieves Category.
*
* @return The category.
*/
public Category getCategory() {
return this.category;
}
/**
* Retrieves the headline.
*
* @return The headline.
*/
public String getHeadline() {
return (this.headline != null ? this.headline.trim() : this.headline);
}
/**
* Gets the lock date date of the Article.
*
* @return Lock date for this Article.
*/
public java.util.Date getLockDate() {
return this.lockDate;
}
/**
* Retrieves the summary.
*
* @return The summary.
*/
public String getSummary() {
return this.summary;
}
/**
* Retrieves the teaser.
*
* @return The teaser.
*/
public String getTeaser() {
return this.teaser;
}
/**
* Retrieves the body.
*
* @return The body.
*/
public String getBody() {
return this.body;
}
/**
* Retrieves the flash script.
*
* @return the flash script.
*/
public String getFlashScript() {
return this.flashScript;
}
/**
* Retrieves the flash teaser script.
*
* @return the flash teaser script.
*/
public String getFlashTeaserScript() {
return this.flashTeaserScript;
}
/**
* @return Returns the mobileSite.
*/
public boolean isMobileArticle() {
return mobileArticle;
}
/**
* @return Returns the googleVideoArticle.
*/
public boolean isGoogleVideoArticle() {
return googleVideoArticle;
}
/**
* @return the flashFile
*/
public String getFlashFile() {
return flashFile;
}
/**
* @return the teaserFlashFile
*/
public String getTeaserFlashFile() {
return teaserFlashFile;
}
/**
* @return the flashBase
*/
public String getFlashBase() {
return flashBase;
}
/**
* @return the teaserFlashBase
*/
public String getTeaserFlashBase() {
return teaserFlashBase;
}
/**
* @return the shockwave
*/
public boolean isShockwave() {
return shockwave;
}
/**
* @return Returns the podcastArticle.
*/
public boolean isPodcastArticle() {
return podcastArticle;
}
/**
* @return the averageRating
*/
public double getAverageRating() {
return this.detailObjectMetaInformation.getRatingInfo().getAverageRating();
}
/**
* Populate the average rating for this article
*
* @param connection
*/
public void populateRatingInfo(Connection connection) {
try {
Rating rating = Rating.getRating(this, this.ownedBy, connection);
if(rating != null) {
this.detailObjectMetaInformation.getRatingInfo().populateRatingInfo(rating);
}
} catch (Exception e) {
logger.error("Could not get a rating for article id : " + this.articleId);
}
}
/**
* @return the viewCount
*/
public int getViewCount() {
return this.detailObjectMetaInformation.getTrackingInfo().getTotalViews();
}
/**
* Populate the number of times this article has been viewed.
*
* @param connection
*/
public void populateTrackingInfo(Connection connection) {
try {
TrackingCategory trackingCategory = TrackingCategory.getTrackingCategory(1, connection, logger);
TrackingAsset asset = TrackingAsset.getTrackingAsset(trackingCategory,
this.usedBy.getId(),
PageElement.DETAIL_TYPE_ARTICLE_ID,
this.articleId,
true, // load the instance from memory cache if possible.
connection,
logger);
if(asset != null) {
this.detailObjectMetaInformation.getTrackingInfo().populateTrackingInfo(asset);
}
} catch (Exception e) {
logger.error("Could not get a tracking asset for article id : " + this.articleId);
}
}
/**
* @return Returns the googleVideoProcessedDate.
*/
public Date getGoogleVideoProcessedDate() {
return googleVideoProcessedDate;
}
/**
* Return the url of the external sources this article is linked to.
*/
public String getUrl() {
return this.url;
}
/**
* Returns boolean value indicating whether this <code>Article</code> is to be
* shared across all sites or not.
*
* @return <code>true</code> if this <code>Article</code> is shared across all
* sites, false otherwise.
*/
public boolean isGeneralArticle() {
return this.generalArticle;
}
/**
* Returns boolean value indicating whether this <code>Article</code> is to be
* shown on a HomePage or not.
*
* @return <code>true</code> if this <code>Article</code> is to be shown on
* the Homepage, false otherwise.
*/
public boolean isOnHomePage() {
return this.onHomePage;
}
/**
* Determines whether this article is available for syndication
*
* @return <code>true</code> if this <code>Article</code> is available for
* syndication.
*/
public boolean isSyndicated() {
return this.syndicated;
}
/**
* Returns true if the article is live (the article has been launched and
* it's date and posted date are after the current time, and the exiry date
* (if present) has not past.
*
* @return true if the article is live.
*/
public boolean isLive() {
boolean live = false;
if (null != this.sitePostedDate) {
Date now = new Date();
live = now.after(this.sitePostedDate) && now.after(this.articleDate) &&
(null != this.siteRemovedDate ? now.before(this.siteRemovedDate) :
true);
}
return live;
}
/**
* Returns true if the article has expired.
*
* @return true if the article has expired.
*/
public boolean isExpired() {
boolean expired = false;
if (null != this.siteRemovedDate) {
Date now = new Date();
expired = now.after(this.siteRemovedDate);
}
return expired;
}
/**
* Return true if this article is available for alerts, i.e. whether it will
* be picked up by the desktop alert application.
*
* @return True if this article is available for alerts.
*/
public boolean isAvailableForAlerts() {
return availableForAlerts;
}
/**
* Return true if this article has media associated with it.
*
* @return True true if this article has media associated with it.
*/
public boolean isMediaArticle() {
return (this.isVideoArticle() || this.isAudioArticle());
}
/**
* Return true if this article has any video associated with it.
*
* @return True true if this article has any video associated with it.
*/
public boolean isVideoArticle() {
return (this.videoHi != null || this.videoMedium != null ||
this.videoLo != null || this.videoDownload != null
|| (null != getLinkedVideos() && getLinkedVideos().length > 0));
}
/**
* Return true if this article has any events associated with it.
*
* @return True true if this article has any video associated with it.
*/
public boolean isEventArticle() {
return (null != linkedObjects &&
this.linkedObjects.get(DetailType.EVENT_KEY) != null &&
this.linkedObjects.get(DetailType.EVENT_KEY).size() > 0);
}
/**
* Return true if this article has any events associated with it that have
* been flagged as FREE by ANY of the site partners.
* @return True true if this article has any free videos associated with it.
*/
public boolean isFreeEventArticle() {
boolean isFree = false;
if(this.linkedObjects != null &&
this.linkedObjects.get(DetailType.EVENT_KEY) != null &&
!this.linkedObjects.get(DetailType.EVENT_KEY).isEmpty() ) {
if(this.linkedObjects.get(DetailType.EVENT_KEY).get(0)!=null) {
if(this.linkedObjects.get(DetailType.EVENT_KEY).get(0) instanceof EventDetail){
EventDetail tempEvent = (EventDetail)this.linkedObjects.get(DetailType.EVENT_KEY).get(0);
for(EventForPartnerBase eventPartner : tempEvent.getEventForPartners()) {
if(eventPartner.isFreeEvent()) {
isFree = true;
}
}
}
}
}
return isFree;
}
/**
* Return true if this article has any audio associated with it.
*
* @return True true if this article has any audio associated with it.
*/
public boolean isAudioArticle() {
return (this.audioStream != null);
}
/**
* Sets whether this article is available for alerts. If the state has
* changed this method also sets the alertStatusChanged - this used by
* the save method to determine whether the news feed should be flushed.
*
* @return True if this article is available for alerts.
*/
public void setAvailableForAlerts(boolean availableForAlerts) {
if (this.availableForAlerts != availableForAlerts) {
this.availableForAlerts = availableForAlerts;
this.alertStatusChanged = true;
}
}
/**
* Returns low quality video file name associated with this article.
*
* @return low quality video file name associated with this article.
*/
public String getVideoLo() {
return this.videoLo;
}
/**
* Returns medium quality video file name associated with this article.
*
* @return medium quality video file name associated with this article.
*/
public String getVideoMedium() {
return this.videoMedium;
}
/**
* Returns high quality video file name associated with this article.
*
* @return high quality video file name associated with this article.
*/
public String getVideoHi() {
return this.videoHi;
}
/**
* Returns download video file name associated with this article.
*
* @return download video file name associated with this article.
*/
public String getVideoDownload() {
return this.videoDownload;
}
/**
* Returns news alert video file name associated with this article.
*
* @return news alert video file name associated with this article.
*/
public String getVideoNewsAlert() {
//
// News alert filename is extrapolated from the hi quaility video name
//
String video = null;
if (null != this.videoHi) {
video = Pattern.compile("mms://video").matcher(this.videoHi).replaceAll(
"http://download"
);
}
return video;
}
/**
* @return The number of times a video will be played in the media player
*/
public int getVideoPlayCount() {
return this.videoPlayCount;
}
/**
* Return true if the article is flagged for newsletter
*
* @return true if the article is flagged for newsletter
*/
public boolean isDisplayOnNewsletters() {
return this.displayOnNewsletters;
}
/**
* Set whether the article is flagged for newsletters
* @param displayOnNewsletter True if the article is to be included on
* site's newsletter
*/
public void setDisplayOnNewsletter(boolean displayOnNewsletters) {
this.displayOnNewsletters = displayOnNewsletters;
}
/**
* Returns a name for owner's not knock-out competition. The mapping between
* PTV competition and its name used by Orange is defined in
* contentSyndication.properties - competitionName.
*
* .:INCOMPLETE:. EB 11/-7/2005 Another hack method, this should all be
* redundant very soon.
*
* @param propertiesFileName properties file name to look in for mapping.
* @param competitionKey key for finding competitions
* @return competition name for site or null if no match found for PTV site
*/
public String getOrangeCompetitionName(final String propertiesFileName,
final String competitionKey)
throws ClassNotFoundException {
//
// Read the mapping between site short name & Orange long name from the
// properties file.
//
CachedTypedProperties props =
CachedTypedProperties.getInstance(propertiesFileName);
//
// References to the database
//
Connection dbConnection = null;
ConnectionPool pool = null;
Logger logger = Logger.getLogger(Article.class);
String orangeCompetitionName = null;
try {
//
// Set up the database connection using pool specific
// to this controller
//
pool = ConnectionPool.getInstance("Cas",logger);
dbConnection = pool.getConnection(true, logger);
int competitionId = Competition.getCompetitions(
false,
Team.getFirstTeam(ownedBy, dbConnection, logger),
new Date(),
dbConnection,
logger
)[0].getCompetitionSeries().getId();
//
// Get the Orange long name matching the competition (owner's not knock-out
// competition).
//
orangeCompetitionName = props.getProperty(competitionKey + competitionId);
if (null == orangeCompetitionName) {
String warning = "Couldn't find property " +
competitionKey + competitionId +
" in properties file " + propertiesFileName;
LOGGER.error(warning);
//
// The data cannot be syndicated without a mapping for the name. Throw
// an exception.
//
throw new IllegalStateException(warning);
}
} catch (SQLException sqle) {
ErrorLog.write("Database Error getting competition name: " + sqle,
this.getClass(),
Level.ERROR,
logger);
} finally {
//
// If the database connection is still open, return it.
//
if (null != dbConnection) {
try {
pool.returnConnection(dbConnection, logger);
} catch (SQLException sqle) {
ErrorLog.write("Database Error when generating XML: " + sqle,
this.getClass(),
Level.ERROR,
logger);
} finally {
dbConnection = null;
}
}
}
return orangeCompetitionName;
}
/**
* Returns name of site that owns this article. PTV common name is used.
* @return name of site that owns this article.
*/
public String getOrangeClubName() {
//
// .:INCOMPLETE:. EB 11/07/2005 This is just a very very bad hack for now,
// this bit of code should be soon redundant, so we're hardcoding this bit
// for now.
//
return (10304 == getOwnedBy().getId() ? "Sheffield Wednesday" :
getOwnedBy().getCommonName());
}
/**
* Returns acronym for site. The mapping between PTV site short code & Orange
* acronym is defined in propertiesFileName.
*
* .:REVIEW:. MS 23/09/2004 Orange and other service providers require us
* to use their own particular site names. This information should be
* stored in the database, either as an extra column on organisations, or as
* another table/object combination to map from a site to a vendor specific
* name or id. This will allow this method and the one above to be
* deprecated.
*
* @param propertiesFileName properties file name to look in for mapping.
* @param acronymKey key for finding acronyms
* @return acronym for site or null if no match found for PTV site
*/
public String getOrangeClubAcronym(final String propertiesFileName,
final String acronymKey)
throws ClassNotFoundException {
//
// Read the mapping between site short name & Orange long name from the
// properties file.
//
CachedTypedProperties props =
CachedTypedProperties.getInstance(propertiesFileName);
//
// Get the Orange long name matching the site (which owns this article)
// short name.
//
String orangeClubName = props.getProperty(acronymKey +
getOwnedBy().getShortName());
if (null == orangeClubName) {
String warning = "Couldn't find property " +
acronymKey + getOwnedBy().getShortName() +
" in properties file " + propertiesFileName;
LOGGER.error(warning);
//
// The data cannot be syndicated without a mapping for the name. Throw
// an exception.
//
throw new IllegalStateException(warning);
}
return orangeClubName;
}
/**
* Returns Syndicated Time in XML Schema DateTime format CCYY-MM-DDThh:mm:ss
* (see http://www.w3.org/TR/2001/REC-xmlschema-2-20010502/#dateTime).
* The time when this method runs is used as the syndicated timestamp.
*
* @return time stamp when the match was syndicated
*/
protected String getSyndicatedXSDateTime() {
String xsDateTimeStr = null;
if ( null == this.syndicatedTimeStamp ) {
xsDateTimeStr =
Constants.General.SYND_DATE_FORMAT.format(new java.util.Date());
} else {
xsDateTimeStr =
Constants.General.SYND_DATE_FORMAT.format(getSyndicatedTimeStamp());
}
return xsDateTimeStr;
}
/**
* Set Syndicated Time Stamp.
*
* @param timeStamp time when this article became syndicated.
*/
public void setSyndicatedTimeStamp(java.util.Date timeStamp) {
this.syndicatedTimeStamp = timeStamp;
updateLastEditDate = true;
}
/**
* Returns UnSyndicated Time in XML Schema DateTime format CCYY-MM-DDThh:mm:ss
* (see http://www.w3.org/TR/2001/REC-xmlschema-2-20010502/#dateTime).
* The time when this method runs is used as the syndicated timestamp.
*
* @return time stamp when the match was syndicated
*/
protected String getUnSyndicatedXSDateTime() {
String xsDateTimeStr = null;
if ( null == this.unSyndicatedTimeStamp ) {
xsDateTimeStr =
Constants.General.SYND_DATE_FORMAT.format(new java.util.Date());
} else {
xsDateTimeStr =
Constants.General.SYND_DATE_FORMAT.format(this.unSyndicatedTimeStamp);
}
return xsDateTimeStr;
}
/**
* Set UnSyndicated Time Stamp.
*
* @param timeStamp time when this article became unSyndicated.
*/
public void setUnSyndicatedTimeStamp(java.util.Date timeStamp) {
this.unSyndicatedTimeStamp = timeStamp;
updateLastEditDate = true;
}
/**
* Return the time when this article became syndicated.
*
* @return
*/
public java.util.Date getSyndicatedTimeStamp() {
return this.syndicatedTimeStamp;
}
/**
* Return the time when this article became syndicated.
*
* @return
*/
public java.util.Date getUnSyndicatedTimeStamp() {
return this.unSyndicatedTimeStamp;
}
/**
* Returns name of XML Meta Data file for this article. Note this filename
* relates to the file generated as a result of XSLT transformation of the
* XML data generated from method getXml().
* The naming convention is as agreed with Orange :-
* i.e. <site which owns article>_<article id>_<timestamp>.xml.
*
* @param propertiesFileName properties file name to use
* @param filenameKey first part of key to use to retrieve Orange site name
* @return Name of XML Meta Data file for this article
*/
public String getMetaDataXmlFileName(final String propertiesFileName,
final String filenameKey) {
//
// Read the mapping between PTV site short name & Orange site name from
// the properties file.
//
CachedTypedProperties props = null;
try {
props = CachedTypedProperties.getInstance(propertiesFileName);
} catch (ClassNotFoundException cnfe) {
LOGGER.fatal("Couldn't find properties file " +
propertiesFileName, cnfe);
}
//
// Get the Orange long name matching the site (which owns this article)
// short name.
//
String orangeClubName = props.getProperty(filenameKey +
getOwnedBy().getShortName());
if (null == orangeClubName) {
LOGGER.error("Couldn't find property " +
filenameKey + getOwnedBy().getShortName() +
" in properties file " + propertiesFileName);
return null;
}
String filename = orangeClubName + "_" +
getArticleId() + "_" +
XML_FILENAME_DATETIME_FORMAT.format(
getSyndicatedTimeStamp()
) + XML_FILE_EXTENSION;
return filename;
}
/**
* Returns audio stream name associated with this article.
*
* @return audio stream name name associated with this article.
*/
public String getAudioStream() {
return this.audioStream;
}
/**
* Returns encoded audio stream name associated with this article.
*
* @return encoded audio stream name name associated with this article.
*/
public String getEncodedAudioStream() {
return Rot13.rotate(this.audioStream);
}
/**
* Returns encoded low quality video file name associated with this article.
*
* @return encoded low quality video file name associated with this article.
*/
public String getEncodedVideoLo() {
return Rot13.rotate(this.videoLo);
}
/**
* Returns encoded high quality video file name associated with this article.
*
* @return encoded high quality video file name associated with this article.
*/
public String getEncodedVideoHi() {
return Rot13.rotate(this.videoHi);
}
/**
* Returns encoded medium quality video file name associated with this article.
*
* @return encoded medium quality video file name associated with this article.
*/
public String getEncodedVideoMedium() {
return Rot13.rotate(this.videoMedium);
}
/**
* This method returns the <code>CommentGroup</code> associated with
* this article.
*
* @return A <code>CommentGroup</code> instance.
*/
public CommentGroup getCommentGroup() {
return this.commentGroup;
}
/**
* This method sets the <code>CommentGroup</code> for this article
* @param commentGroup <code>CommentGorup</code> for this article
*/
public void setCommentGroup(CommentGroup commentGroup) {
this.commentGroup = commentGroup;
}
/**
* Returns <code>Page</code> used to display this <code>Article</code>.
*
* @return <code>Page</code> used to display this <code>Article</code>.
*/
public Page getPage() {
return this.page;
}
/**
* Returns a <code>Curl</code> object for this particular article.
* @return <code>Curl</code> used to display a link.
*
* @throws URISyntaxException Exception object encapsulating a URI syntax
* exception, thrown when we encounter a page with an invalid path.
* @throws InvalidCurlException Exception object encapsulating an invalid
* CURL exception, thrown when we encounter a page with an invalid
* path.
*/
public Curl getCurl() {
return (null == this.page ? null : this.page.getCurl(this.detailObjectForCurl));
}
/**
* @return The curl for articles on the Player/Denmark part of the site or null if the article is not available in
* player or if we are unable to resolve an appropriate page/Curl;
*/
public Curl getPlayerCurl() {
// The Latest news page (in its current implementation) needs to know if it is displaying
// an article or a video (match highlight) so we supply the detail type of what is being displayed.
return (null == this.playerPage ? null : this.playerPage.getCurl(this.detailObjectForCurl.getId(),
this.detailObjectForCurl.getDetailTypeId()));
}
/**
* Determines, by application lookup name, the correct page for linking articles that are available to player
*
* @param dbConnection
*/
private void setPlayerNewsPage(Connection dbConnection) {
CachedTypedProperties properties = null;
String lookupName = null;
// We use a property to define the Page category Lookup name so that we can change this in the future if needed.
try {
properties = CachedTypedProperties.getInstance(ARTICLE_PROPERTIES_FILE_NAME);
lookupName = properties.getStringProperty("player.newsPageCategoryLookupName");
} catch (ClassNotFoundException e) {
LOGGER.error("Unable to load properties file with name '" + ARTICLE_PROPERTIES_FILE_NAME + "'" , e);
}
Page newsPage = null;
// Assuming we have a lookup name we try and resolve the page.
if(!StringUtils.isNullOrEmpty(lookupName)) {
try {
newsPage = Page.getPage(lookupName, PageElement.DETAIL_TYPE_ARTICLE_ID, this.getOwnedBy(), dbConnection, LOGGER);
} catch (SQLException e) {
LOGGER.error("Unable to retrieve page with category lookup name '"
+ lookupName + "' for Article with id '"
+ this.detailObjectForCurl.getId() + "'", e);
}
}
this.playerPage = newsPage;
}
/**
* @return The curl id is the numeric id that appears in the curl
* used to build a link to display this article.
*/
public int getCurlId() {
return this.curlId;
}
/**
* Retrieves the site that wrote the article represented by this bean. (Only
* this site and PTV editors are allowed to edit the article.)
* @return The Site that wrote the article represented by this bean.
*/
public Site getOwnedBy() {
//
// It is a coding error to allow the construction of an article with this
// attribute unset. Assert therefore that it is set when we try to retrieve
// it.
//
assert (null != this.ownedBy);
return this.ownedBy;
}
/**
* Retrieves the site that is using this bean: may be different from
* {@link #ownedBy} if this article is being displayed on another site's
* site (a site other than the site that wrote it).
* @return The Site that is currently using this article.
*/
public Site getUsedBy() {
//
// It is a coding error to allow the construction of an article with this
// attribute unset. Assert therefore that it is set when we try to retrieve
// it.
//
assert (null != this.usedBy);
return this.usedBy;
}
/**
* Retrieves the Site Posted Date.
*
* @return The sitePostedDate. If there is no site posted date information
* existing on a database, it returns null.
*/
public java.util.Date getSitePostedDate() {
return this.sitePostedDate;
}
/**
* Retrieves the last Edited Date.
*
* @return The last edit date. If there is no last edit date information
* existing on a database, it returns null.
*/
public java.util.Date getLastEditDate() {
return this.lastEditDate;
}
/**
* Formats a date according to the Premier League's requirements
*
* @param inputDate The date to be formatted.
* @return The formatted string. If the date is null,
* it returns an empty string.
*/
private String formatDateForPremierLeague(Date inputDate)
{
String formattedDate = "";
if (null!=inputDate)
{
SimpleDateFormat normalFormat = new SimpleDateFormat("EEE dd MMM yyyy");
formattedDate = normalFormat.format(inputDate);
formattedDate=formattedDate.replace("Tue ", "Tues ");
formattedDate=formattedDate.replace("Wed ", "Weds ");
formattedDate=formattedDate.replace("Thu ", "Thurs ");
}
return formattedDate;
}
/**
* Retrieves the Site Posted Date formatted according to the Premier League's requirements
*
* @return The sitePostedDate string. If there is no site posted date information
* existing on a database, it returns an empty string.
*/
public String getSitePostedDateFormattedForPremierLeague() {
return formatDateForPremierLeague(sitePostedDate);
}
/**
* Retrieves the lastEditDate formatted according to the Premier League's requirements
*
* @return The lastEditDate string. If there is no last edit date information
* existing on a database, it returns an empty string.
*/
public String getLastEditDateFormattedForPremierLeague() {
return formatDateForPremierLeague(lastEditDate);
}
/**
* Retrieves the Site Removed Date.
*
* @return The siteRemovedDate. If there is no site posted date information
* existing on a database, it returns null.
*/
public java.util.Date getSiteRemovedDate() {
return this.siteRemovedDate;
}
/**
* Retrieves the header image associated with this article. Note that the
* image is only instantiated if getArticleImages has been called explicitly.
* If this call has not been made, or if no image is associated, null will be
* returned.
*
* @return Article header Image.
*/
public Image getHeaderImage() {
return this.headerImage;
}
/**
* Returns this article's header image path.
* @return this article's header imageg path.
*/
public String getHeaderImagePath() {
return getHeaderImagePath(null);
}
/**
* Returns this article's header image path. If the header image has
* not been instantiated, it load it from database using the pass in
* connection if it is not null or by creating a new db connection if
* the passed in one is null.
*
* @param dbConnection Connection to load the header image from database
* in case it is not yet loaded.
*
* @return this article's header image path.
*/
public String getHeaderImagePath(Connection dbConnection) {
String returnString = "";
boolean connectionCreated = false;
if (null != this.headerImage) {
returnString = this.headerImage.getPath();
} else {
if (this.headerImageId != 0) {
try {
if (dbConnection == null) {
dbConnection = PTVHelper.getConnection();
connectionCreated = true;
}
this.headerImage = Image.getImage(this.headerImageId, dbConnection, logger);
returnString = this.headerImage.getPath();
} catch (SQLException sqle) {
logger.error("There was a problem retrieving Header Image", sqle);
} catch (ClassNotFoundException cnfe) {
logger.error("There was a problem retrieving Header Image", cnfe);
} finally {
if (connectionCreated) {
PTVHelper.releaseConnection(dbConnection);
}
}
}
}
return returnString;
}
/**
* Retrieves the teaser image associated with this article. Note that the
* image is only instantiated if getArticleImages has been called explicitly.
* If this call has not been made, or if no image is associated, null will be
* returned.
*
* @return Article teaser Image.
*/
public Image getTeaserImage() {
return this.teaserImage;
}
/**
* Returns this article's teaser image path.
* @return this article's teaser image path.
*/
public String getTeaserImagePath() {
return getTeaserImagePath(null);
}
/**
* Returns this article's teaser image path. If the teaser image has
* not been instantiated, it load it from database using the pass in
* connection if it is not null or by creating a new db connection if
* the passed in one is null.
*
* @param dbConnection Connection to load the teaser image from database
* in case it is not yet loaded.
*
* @return this article's header image path.
*/
public String getTeaserImagePath(Connection dbConnection) {
String returnString = "";
boolean connectionCreated = false;
if (null != this.teaserImage) {
returnString = this.teaserImage.getPath();
} else {
if (this.teaserImageId != 0) {
try {
if (dbConnection == null) {
dbConnection = PTVHelper.getConnection();
connectionCreated = true;
}
this.teaserImage = Image.getImage(this.teaserImageId, dbConnection, logger);
returnString = this.teaserImage.getPath();
} catch (SQLException sqle) {
logger.error("There was a problem retrieving Teaser Image", sqle);
} catch (ClassNotFoundException cnfe) {
logger.error("There was a problem retrieving Teaser Image", cnfe);
} finally {
if (connectionCreated) {
PTVHelper.releaseConnection(dbConnection);
}
}
}
}
return returnString;
}
/**
* Retrieves the mobile image associated with this article. Note that the
* image is only instantiated if getArticleImages has been called explicitly.
* If this call has not been made, or if no image is associated, null will be
* returned.
*
* @return Article mobile Image.
*/
public Image getMobileImage() {
return this.mobileImage;
}
/**
* Retrieves the holding image to display if video is stopped.
*
* @return Video holiding image.
*/
public Image getVideoHoldingImage() {
return this.videoHoldingImage;
}
/**
* Return the images linked to the article.
*
* Linked images are stored in a separate table unlike the header and teaser
* images, since there may be a variable number of them. They're intended to
* be displayed on the article detail page, as an alternative to embedding the
* image links inside the html. This functionality was originally developed
* for the nobok site. Please note that because of the extra over head
* involved in setting these images up, they're only retrieved when a single
* article is returned using Article.getArticle and
* Site.isArticleImageLinksEnabled() == true;
*
* @return Array of images linked to the article.
*/
public Image[] getLinkedImages() {
return this.linkedImages;
}
/**
* Additional images to associate with the article - see getLinkedImages for
* details
*/
public void setLinkedImages(Image[] images) {
this.linkedImages = images;
}
/**
* Helper method to add an image to the array
* @param image
*/
public void addLinkedImage(Image image) {
Set<Image> imageList = new LinkedHashSet<Image>();
if (null != linkedImages) {
imageList.addAll(Arrays.asList(linkedImages));
}
imageList.add(image);
Image[] tempLinkedImages = new Image[imageList.size()];
imageList.toArray(tempLinkedImages);
this.linkedImages = tempLinkedImages;
}
/**
* Return the videos linked to the article.
*
* @return Array of videos linked to the article.
*/
public Video[] getLinkedVideos() {
return this.linkedVideos;
}
/**
* Additional videos to associate with the article - see getLinkedVideos for
* details
*/
public void setLinkedVideos(Video[] videos) {
this.linkedVideos = videos;
}
public boolean getHasLinkedVideos() {
return !ArrayUtils.isEmpty(this.linkedVideos);
}
/**
* returns the name we should use for describing the mobile image file
* when syndicating it to Orange.
*
* @return String name the image file should be saved as during syndication.
*/
public String getMobileImageSyndicationName() {
return this.articleId +
"_" +
this.mobileImage.getId() +
"." +
Image.JPG_EXTENSION;
}
/**
* Sets the Article Date.
*
* @param articleDate Date on which the article is being created/updated.
*/
public void setArticleDate(java.util.Date articleDate) {
this.articleDate = articleDate;
}
/**
* Sets the Site Removed (expiry) Date.
*
* @param articleDate Date on which the article is being created/updated.
*/
public void setSiteRemovedDate(java.util.Date date) {
this.siteRemovedDate = date;
updateLastEditDate = true;
}
/**
* Sets the Site posted (launch) Date. An article is launched by setting
* this field to a past date and then saving the article
*
* @param articleDate Date on which the article is being created/updated.
*/
public void setSitePostedDate(java.util.Date date) {
this.sitePostedDate = date;
}
/**
* Sets the lock date.
*
* @param lockDate Date on which the article was locked.
*/
public void setLockDate(java.util.Date lockDate) {
this.lockDate = lockDate;
}
/**
* Sets the article id.
* @param articleId The article id.
*/
public void setArticleId(int articleId) {
this.articleId = articleId;
}
/**
* Sets this <code>Article's</code> Category to the given category.
*
* @param category Category for this <code>Article</code>.
*/
public void setCategory(Category category) {
this.priorCategory = this.category;
this.category = category;
updateLastEditDate = true;
}
/**
* Sets this <code>Article's</code> page.
*
* @param page Page for this <code>Article</code>
*/
public void setPage(Page page) {
this.page = page;
}
/**
* Sets the headline.
*
* @param headline Text of the headline.
*/
public void setHeadline(String headline) {
this.headline = headline;
updateLastEditDate = true;
}
/**
* Sets the summary.
*
* @param summary Text of the summary.
*/
public void setSummary(String summary) {
this.summary = summary;
updateLastEditDate = true;
}
/**
* Sets the teaser.
*
* @param teaser Text of the teaser.
*/
public void setTeaser(String teaser) {
this.teaser = teaser;
updateLastEditDate = true;
}
/**
* Sets the body.
*
* @param body Text of the body.
*/
public void setBody(String body) {
this.body = body;
updateLastEditDate = true;
}
/**
* Sets the flash script.
*
* @param flashScript The flash string.
*/
public void setFlashScript(String flashScript) {
this.flashScript = flashScript;
}
/**
* Sets the flash teaser script.
*
* @param flashTeaserScript.
*/
public void setFlashTeaserScript(String flashTeaserScript) {
this.flashTeaserScript = flashTeaserScript;
}
/**
* Set the url for this article
*/
public void setUrl(String url) {
this.url = url;
updateLastEditDate = true;
}
/**
* Sets the onHomePage flag
*
* @param onHomePage True if the article should appear on the homepage,
* false otherwise.
*/
public void setOnHomePage(boolean onHomePage) {
if (this.onHomePage != onHomePage) {
this.homePageStatusChanged = true;
this.onHomePage = onHomePage;
}
updateLastEditDate = true;
}
/**
* Sets the syndicated flag
*
* @param syndicated True if the article should syndicated, false otherwise.
*/
public void setSyndicated(boolean syndicated) {
unsyndicated = (this.syndicated && !syndicated);
this.syndicated = syndicated;
updateLastEditDate = true;
}
/**
* Sets the lockId
*
* @param lockId New lock Id.
*/
public void setLockId(int lockId) {
this.lockId = lockId;
}
/**
* Sets the site that wrote the article represented by this
* bean. Only this site and PTV Editors are allowed to edit the article.
* @param ownedBy Sets the site that this article is owned by.
*/
public void setOwnedBy(Site ownedBy) {
//
// This method will only be called when we're creating a new article and
// that can only be done by the owning site, which will also be using the
// article. It is therefore correct that the the 'usedBy' attribute should
// be set here also.
//
this.usedBy = ownedBy;
this.ownedBy = ownedBy;
updateLastEditDate = true;
}
/**
* Sets the site that this article is used by
* @param usedBy Sets the site that this article is used by.
*/
public void setUsedBy(Site usedBy) {
this.usedBy = usedBy;
}
/**
* Sets the low quality video file name related to this article.
*
* @param videoLo the low quality video file name related to this
* article.
*/
public void setVideoLo(String videoLo) {
this.videoLo = videoLo;
updateLastEditDate = true;
}
/**
* Sets the medium quality video file name related to this article.
*
* @param videoHi the medium quality video file name related to this
* article.
*/
public void setVideoMedium(String videoMedium) {
this.videoMedium = videoMedium;
updateLastEditDate = true;
}
/**
* Sets the high quality video file name related to this article.
*
* @param videoHi the high quality video file name related to this
* article.
*/
public void setVideoHi(String videoHi) {
this.videoHi = videoHi;
updateLastEditDate = true;
}
/**
* Sets the download video file name related to this article.
*
* @param videoDownload the video file name related to this article.
*/
public void setVideoDownload(String videoDownload) {
this.videoDownload = videoDownload;
updateLastEditDate = true;
}
/**
* Set the number of times video should play in the media player
*
* @param playCount Number of times video should play in the media player
*/
public void setVideoPlayCount(int playCount) {
this.videoPlayCount = playCount;
}
/**
* Sets the audio stream name related to this article.
*
* @param audioStream the video file name related to this article.
*/
public void setAudioStream(String audioStream) {
this.audioStream = audioStream;
}
/**
* Determines whether this article is shared across all other sites or not.
*
* @param isGeneralArticle true if this article is to be shared across all
* sites.
*/
public void setIsGeneralArticle(boolean isGeneralArticle) {
this.generalArticle = isGeneralArticle;
updateLastEditDate = true;
}
/**
* Set the teaser image associated with this article.
*
* @param teaser Image to associate with this Article.
*/
public void setTeaserImage(Image image) {
this.teaserImage = image;
if (null != image) {
this.teaserImageId = image.getId();
} else {
this.teaserImageId = Article.NO_IMAGE_ID;
}
updateLastEditDate = true;
}
/**
* Set the mobile image associated with this article.
*
* @param mobile Image to associate with this Article.
*/
public void setMobileImage(Image image) {
this.mobileImage = image;
if (null != image) {
this.mobileImageId = image.getId();
} else {
this.mobileImageId = Article.NO_IMAGE_ID;
}
updateLastEditDate = true;
}
/**
* Set the video holding image.
*
* @param image Video holding image.
*/
public void setVideoHoldingImage(Image image) {
this.videoHoldingImage = image;
if (null != image) {
this.videoHoldingImageId = image.getId();
} else {
this.videoHoldingImageId = Article.NO_IMAGE_ID;
}
}
/**
* Set the header image associated with this article.
*
* @param header Image to associate with this Article.
*/
public void setHeaderImage(Image image) {
this.headerImage = image;
if (null != image) {
this.headerImageId = image.getId();
} else {
this.headerImageId = Article.NO_IMAGE_ID;
}
}
/**
* Set the page rank position for this article
*
* @param rank Rank position
*/
public void setPageRank(int rank) {
if (this.pageRank != rank) {
this.pageRankUpdated = true;
}
this.pageRank = rank;
}
/**
* Set the home page rank position for this article.
*
* @param rank Rank position
*/
public void setHomePageRank(int rank) {
//
// Treat an article moving from RANK_NOT_INITIALISED to NOT_RANKED as not
// having been updated - in such a case, existing rankings do not need to
// be updated
//
if (this.homePageRank != rank) {
this.homePageRankUpdated = true;
}
this.homePageRank = rank;
}
/**
* Return the page rank position for this article. If the ranks have not
* been initialised, a run time exception is thrown
*
* @return Page rank position
*/
public int getPageRank() {
if (RANK_NOT_INITIALISED == this.pageRank) {
throw new IllegalStateException("Ranks have not been initialised");
}
return this.pageRank;
}
/**
* Return the home page rank position for this article. If the ranks have not
* been initialised, a run time exception is thrown
*
* @return Home page rank position
*/
public int getHomePageRank() {
if (RANK_NOT_INITIALISED == this.homePageRank) {
throw new IllegalStateException("Ranks have not been initialised");
}
return this.homePageRank;
}
/**
* @param mobileSite The mobileSite to set.
*/
public void setMobileArticle(boolean mobileSiteFlag) {
this.mobileArticle = mobileSiteFlag;
}
/**
* @param googleVideoArticle The googleVideoArticle to set.
*/
public void setGoogleVideoArticle(boolean googleVideoArticle) {
this.googleVideoArticle = googleVideoArticle;
}
/**
* @param googleVideoProcessedDate The googleVideoProcessedDate to set.
*/
public void setGoogleVideoProcessedDate(Date googleVideoProcessedDate) {
this.googleVideoProcessedDate = googleVideoProcessedDate;
}
/**
* @return the availableToPlayer
*/
public boolean isAvailableInPlayer() {
return availableInPlayer;
}
/**
* @param availableToPlayer the availableToPlayer to set
*/
public void setAvailableInPlayer(boolean availableInPlayer) {
this.availableInPlayer = availableInPlayer;
}
/**
* @param flashFile the flashFile to set
*/
public void setFlashFile(String flashFile) {
this.flashFile = flashFile;
if(this.flashFile != null) {
//
// if the flash file ends with .dcr then it is a shockwave file
//
this.shockwave = this.flashFile.toLowerCase().endsWith(".dcr");
try {
this.flashBase = this.flashFile.substring(0, this.flashFile.lastIndexOf("/") + 1);
} catch(Exception e) {
LOGGER.error("Could not set flash base for : " + flashFile + ", " + e.getMessage(), e);
}
}
}
/**
* @param teaserFlashFile the teaserFlashFile to set
*/
public void setTeaserFlashFile(String teaserFlashFile) {
this.teaserFlashFile = teaserFlashFile;
if(this.teaserFlashFile != null) {
try {
this.teaserFlashBase = this.teaserFlashFile.substring(0, this.teaserFlashFile.lastIndexOf("/") + 1);
} catch(Exception e) {
LOGGER.error("Could not set flash base for : " + teaserFlashFile + ", " + e.getMessage(), e);
}
}
}
/**
* @param podcastArticle The podcastArticle to set.
*/
public void setPodcastArticle(boolean podcastArticle) {
this.podcastArticle = podcastArticle;
}
/**
* Set the article keywords.
*
* @param relatedArticleKeywords Array of keywords
*/
public void setRelatedArticleKeywords(String[] relatedArticleKeywords) {
this.relatedArticleKeywords = relatedArticleKeywords;
updateLastEditDate = true;
}
/**
* Returns the keywords associated with this article.
*/
public String[] getRelatedArticleKeywords() {
if (null == this.relatedArticleKeywords) {
throw new IllegalStateException(
"Attempt was made to access article keywords before they have been " +
" instantiated. "
);
}
return this.relatedArticleKeywords;
}
/**
* Method to return the keywords associated with the article. These are
* not instantiated when the article is returned and must be explicitly
* retrieved.
*
* @param dbConnection Database Connection
* @param logger Logger for reporting
*
* @return Array of keyword, or an empty array if no keywords are bound to
* the article.
*/
public String[] getRelatedArticleKeywords(Connection dbConnection,
Logger logger)
throws SQLException {
this.relatedArticleKeywords = new String[0];
PreparedStatement query = ConnectionPool.prepareStatement(
"SELECT keyword, rank " +
"FROM article_keywords " +
"WHERE artl_id = ? " +
"AND orgn_id = ? ",
ResultSet.TYPE_SCROLL_INSENSITIVE,
ResultSet.CONCUR_READ_ONLY,
dbConnection,
logger
);
try {
query.setInt(1, this.articleId);
query.setInt(2, this.usedBy.getId());
ResultSet rs = ConnectionPool.executeQuery(query, dbConnection, logger);
if (rs.last()) {
this.relatedArticleKeywords = new String[rs.getRow()];
rs.beforeFirst();
for (int ii = 0; rs.next(); ++ii) {
this.relatedArticleKeywords[ii] = rs.getString("keyword");
if (rs.getInt("rank") != 0)
this.relatedArticleKeywords[ii] += ":" + rs.getInt("rank");
}
}
} finally {
query.close();
}
return this.relatedArticleKeywords;
}
/**
* Set the teaser videos.
*
* @param teaserVideos Array of video paths
*/
public void setTeaserVideos(String[] teaserVideos) {
this.teaserVideos = teaserVideos;
updateLastEditDate = true;
}
/**
* Returns the teaser videos associated with this article.
*/
public String[] getTeaserVideos() throws Exception {
if (null == this.teaserVideos) {
throw new IllegalStateException(
"Attempt was made to access article teaser videos before they have been " +
" instantiated. "
);
}
return this.teaserVideos;
}
/**
* Method to return the teaser videos associated with the article. These are
* not instantiated when the article is returned and must be explicitly
* retrieved.
*
* @param dbConnection Database Connection
* @param logger Logger for reporting
*
* @return Array of video paths, or an empty array if no teaser videos are bound to
* the article.
*/
public String[] getTeaserVideos(Connection dbConnection,
Logger logger)
throws SQLException {
this.teaserVideos = new String[0];
PreparedStatement query = ConnectionPool.prepareStatement(
"SELECT teaser_video " +
"FROM article_teaser_videos " +
"WHERE artl_id = ? ",
ResultSet.TYPE_SCROLL_INSENSITIVE,
ResultSet.CONCUR_READ_ONLY,
dbConnection,
logger
);
try {
query.setInt(1, this.articleId);
ResultSet rs = ConnectionPool.executeQuery(query, dbConnection, logger);
if (rs.last()) {
this.teaserVideos = new String[rs.getRow()];
rs.beforeFirst();
for (int ii = 0; rs.next(); ++ii) {
this.teaserVideos[ii] = rs.getString("teaser_video");
}
}
} finally {
query.close();
}
return this.teaserVideos;
}
/**
* The method fetches articles by date.
* The number of articles can be controlled by specifying maxArtilces.
*
* @param site A <code>ptv.stats.Site</code> associated with the article
* @param date A <code>java.util.Date</code>. If this is supplied as null,
* the method fetches the articles older than the current date,
* otherwise the method will fetch those articles whose date
* is more than supplied one and are older than the current date
* @param startIndex The index, from where to sart looking into articles.
* @param numberOfArticles The number Of Articles to return
* @param dbConnection Database connection
* @param logger Reporting Logger
*
* @return An array of Article
* @throws SQLException
*/
public static Article[] getArticlesForNewsSiteMap(Site site,
int numberOfDaysToGoBack,
int startIndex,
Connection dbConnection,
Logger logger) throws SQLException {
//
// The returned articles.
//
Article[] articles = null;
String query =
"SELECT * " +
"FROM " +
"( " +
"SELECT " +
"artl.*, " +
"rownum rn " +
"FROM " +
"( " +
"SELECT " +
Article.SQL_ARTICLE_COLUMNS +
"FROM " +
"editorial_articles a " +
"WHERE a.orgn_club_id = ? " +
LIVE_ARTICLES_SQL +
"AND a.site_posted_date > sysdate - ? " +
" ORDER BY site_posted_date DESC " +
") artl " +
") ";
if (logger.isDebugEnabled()) {
logger.debug("Fetching articles by date ..");
logger.debug("Start Index : " + startIndex);
logger.debug("Query for fetching artcles : " + query);
}
PreparedStatement preparedStatement = null;
ResultSet rs = null;
try {
preparedStatement = ConnectionPool.prepareStatement(query, dbConnection, logger);
int param = 0;
preparedStatement.setInt(++param, site.getId());
preparedStatement.setInt(++param, numberOfDaysToGoBack);
rs = ConnectionPool.executeQuery(preparedStatement, dbConnection, logger);
ArrayList<Article> articleList = new ArrayList<Article>();
while (rs.next()) {
Article article = new Article();
article.setArticleFields(rs, site, false, dbConnection, logger);
articleList.add(article);
}
if (!ListUtils.isNullOrEmpty(articleList)) {
articles = new Article[articleList.size()];
articles = articleList.toArray(articles);
// All we need is pages, don't bother about the rest
Article.setPages(articles, dbConnection, logger);
}
} finally {
if (null != rs) {
rs.close();
}
if(null != preparedStatement) {
preparedStatement.close();
}
}
if (logger.isDebugEnabled()) {
logger.debug("Number Of Articles Fetched By Date : " + (null != articles ? articles.length : 0));
}
return articles;
}
/**
* Returns an <code>Article</code> when given an <code>article ID</code>
* If no article is found, it returns NULL.<br>
* Article returned will only contain article parts data, such as article
* headline, teaser, body and summary.
*
* @param articleId An int identifying the <code>Article</code>.
* @param liveArticlesOnly
* A boolean indicating whether only live articles or all
* <code>Articles</code> are required. If this flag is set
* to <code>true</code> then only live articles will be
* searched for.
* @param instantiateImages whether to instantiate images linked to this article
* @param usedBy The site that is going to be using this article.
* Only sharable articles or ones written by this site
* will be returned. If null the article will be returned
* regardless of which it belongs to - this should only be
* used for internal tools.
* @param con A <code>Connection</code> to use in the method.
* @param logger A <code>Logger</code> to use in the method.
*
* @return The <code>Article</code> requested, or null if not found.
*
* @exception SQLException if a database access error occurs
*/
public static Article getArticle(int articleId,
boolean liveArticlesOnly,
boolean instantiateImages,
Site usedBy,
Connection con,
Logger logger)
throws SQLException {
logger.debug("Trying to get article for id " + articleId);
Article[] articles = Article.getArticles(new int[] {articleId},
liveArticlesOnly,
instantiateImages,
usedBy,
con,
logger);
Article article = null;
if (null != articles) {
//
// At most one article may be returned
//
assert (1 == articles.length);
article = articles[0];
//
// If the site supports article image links, find the images linked to
// the article.
//
if (null != usedBy && usedBy.isArticleImageLinksEnabled()) {
article.linkedImages = Image.getImages(article, con, logger);
if (null == article.linkedImages) {
article.linkedImages = new Image[0];
}
}
}
return article;
}
/**
* Loads in all of the videos linked to by this article
* @param usedBy The site that is going to be using this article.
* Only sharable articles or ones written by this site
* will be returned. If null the article will be returned
* regardless of which it belongs to - this should only be
* used for internal tools.
* @param conn A <code>Connection</code> to use in the method.
* @param logger A <code>Logger</code> to use in the method.
*/
private void populateLinkedVideos(Site usedBy, Connection conn, Logger logger)
throws ClassNotFoundException, SQLException {
if (null == usedBy || usedBy.isArticleVideoLinksEnabled()) {
setLinkedVideos(Video.getVideos(this, conn, logger));
if (null == getLinkedVideos()) {
setLinkedVideos(new Video[0]);
}
}
}
/**
* Retrieves all videos linked to articles.
*
* @param articles Array of articles for which linked objects are to be
* retrieved
* @param site Site
* @param dbConnection Database connection
* @param logger Logger
*/
private static void getLinkedVideos(Article[] articles,
Site site,
Connection dbConnection,
Logger logger)
throws SQLException {
try {
//
// Due to content sharing the site may be null, in which case load the videos in case they are required
//
if (null == site || site.isArticleVideoLinksEnabled()) {
for (Article article : articles) {
article.populateLinkedVideos(site, dbConnection, logger);
}
}
} catch (ClassNotFoundException e) {
//
// Cast to a SQL exception to match this object's typical throwable interfaces
//
throw new SQLException(e.getMessage());
}
}
/**
* Loads in the video, if any, that 'owns' these articles
* @param articles
*/
private static void getOwningVideos(Article[] articles, Site site, Connection connection, Logger logger)
throws SQLException {
try {
//
// In order to minimise hits on the database, perform both loads as single hits
//
Map<Integer, Article> articleMap = new HashMap<Integer, Article>();
Map<Integer, Article> videoArticleMap = new HashMap<Integer, Article>();
int[] ids = new int[articles.length];
int idx = 0;
for (Article article : articles) {
articleMap.put(article.getArticleId(), article);
ids[idx++] = article.getArticleId();
}
//
// First identify all of the articles that are owned by a video
// and plave them a the videoArticleMap
//
ConnectionPool.getInList(ids);
PreparedStatement stmt =
ConnectionPool.prepareStatement(
"SELECT artl_id, video_id FROM vat_created_article WHERE artl_id in ("
+ConnectionPool.getInList(ids)+")", connection, logger);
try {
ConnectionPool.setInListParameters(stmt, 0, ids);
ResultSet rs = stmt.executeQuery();
try {
while (rs.next()) {
Article article = articleMap.get(rs.getInt("artl_id"));
videoArticleMap.put(rs.getInt("video_id"), article);
}
} finally {
rs.close();
}
} finally {
stmt.close();
}
//
// Then load in all of these owning videos and set the ownership on the article
//
int[] videoIds = new int[videoArticleMap.size()];
idx = 0;
for (Integer videoId : videoArticleMap.keySet()) {
videoIds[idx++] = videoId;
}
if (videoIds.length > 0) {
Video[] videos = Video.getVideos(videoIds, connection, logger);
for (Video video : videos) {
Article article = videoArticleMap.get(video.getId());
if (null != article) {
article.setOwningVideo(video);
}
}
}
} catch (ClassNotFoundException e) {
//
// To avoid massive, unnecessary APi changes, rethrow as a SQLException
//
throw new SQLException (e.getMessage());
}
}
/**
* Returns the articles that are linked to a particular instance of the
* supplied detail type including those articles that belong
* to the syndication partner sites unless the user has somehow managed to
* supply a null or non syndicated category in which case the syndication
* partners are ignored because we can't hope to retrieve relevant articles
* without a category (or a syndicated article category) to correlate the
* articles.
*
* @param detailType
* @param linkId
* @param numberArticlesToReturn
* @param category
* @param maxArticlesPerCategory
* @param ownedAndUsedBy
* @param syndicationPartners
* @param liveArticlesOnly
* @param searchableArticlesOnly
* @param videoInclusion
* @param con
* @param logger
* @return
* @throws SQLException
*/
public static Article[] getArticlesByDetailType(DetailType detailType,
int linkId,
int numberArticlesToReturn,
Category category,
int maxArticlesPerCategory,
Site ownedAndUsedBy,
Collection<Site> syndicationPartners,
boolean liveArticlesOnly,
boolean searchableArticlesOnly,
int videoInclusion,
Connection con,
Logger logger)
throws SQLException {
// We can't expect to get relevant articles if the category is not present or if the supplied category isn't used in
// syndication so in these cases we disable syndicated article retrieval.
boolean includeSyndicationPartners = (syndicationPartners != null
&& !syndicationPartners.isEmpty() && category != null && category.isUsedInSyndication());
if(logger.isDebugEnabled()) {
logger.debug(String.format("Retrieving articles (%s syndication partners) for detail type [%d], link id [%d] and category[%d] for site [%s]",
((includeSyndicationPartners) ? "including" : "excluding"),
detailType.getId(),
linkId, ((null != category) ? category.getCategoryId(): -1),
ownedAndUsedBy.getName()));
}
//
// If multiple articles are linked to the same match, the most recent
// article (determined by article date) is returned.
//
StringBuilder queryString = new StringBuilder("SELECT * FROM ( ");
if(maxArticlesPerCategory != Article.ALL_ARTICLES) {
queryString.append("SELECT a3.*, rownum FROM ( ");
}
queryString.append("SELECT ").append(Article.SQL_ARTICLE_COLUMNS);
//
// If the number of articles per category is restricted, use oracle's
// rank() function to determine the top articles per category. The
// Article id is included in the partition by clause to ensure that
// there are no rank collisions. Otherwise two articles with the same
// date might cause too many articles to be returned for a category.
//
if(maxArticlesPerCategory != Article.ALL_ARTICLES) {
queryString.append(", RANK() OVER ( PARTITION BY a.catg_id ORDER BY article_date DESC, artl_id DESC) categoryRank ");
}
//
// Restrict to searchable categories only if so specified
//
if(searchableArticlesOnly) {
queryString.append("FROM editorial_articles a, categories c WHERE a.catg_id = c.catg_id AND c.search_flg = 'Y' AND a.artl_id IN (");
} else {
queryString.append("FROM editorial_articles a WHERE a.artl_id IN (");
}
//
// If the detail type is for an article, look for articles linked
// to objects linked to the article. Otherwise find articles
// linked to the object specified by the detail type.
//
if (detailType.getId() == PageElement.DETAIL_TYPE_ARTICLE_ID) {
queryString.append("SELECT a2.artl_id FROM article_links a1, article_links a2 WHERE a1.artl_id = ? AND a1.detail_type_id = a2.detail_type_id AND a1.link_id = a2.link_id AND a2.artl_id <> ? ");
} else {
queryString.append("SELECT artl_id FROM article_links WHERE detail_type_id = ? AND link_id = ? ");
}
queryString.append(") ");
if(liveArticlesOnly) {
queryString.append(Article.LIVE_ARTICLES_SQL);
}
if(null != category) {
queryString.append("AND a.catg_id = ? ");
}
if(includeSyndicationPartners) {
queryString.append("AND ((a.orgn_club_id = ?) OR (a.syndicated_flg='Y' AND a.orgn_club_id IN (");
queryString.append(ConnectionPool.getInList(syndicationPartners));
queryString.append("))) ORDER BY DECODE (a.orgn_club_id, ?, 1, 2) ASC, ");
} else {
queryString.append("AND a.orgn_club_id = ? ORDER BY ");
}
queryString.append("article_date DESC ");
if(maxArticlesPerCategory != Article.ALL_ARTICLES) {
queryString.append(") a3 WHERE categoryRank <= ? ");
}
queryString.append(") WHERE ROWNUM <= ? ");
PreparedStatement preparedStatement = ConnectionPool.prepareStatement(queryString.toString(),
con,
logger
);
Article[] articles = null;
try {
int param = 0;
if (detailType.getId() == PageElement.DETAIL_TYPE_ARTICLE_ID) {
preparedStatement.setInt(++param, linkId);
} else {
preparedStatement.setInt(++param, detailType.getId());
}
preparedStatement.setInt(++param, linkId);
if (null != category) {
preparedStatement.setInt(++param, category.getCategoryId());
}
preparedStatement.setInt(++param, ownedAndUsedBy.getId());
if(includeSyndicationPartners) {
for(Site syndicationPartner: syndicationPartners) {
preparedStatement.setInt(++param, syndicationPartner.getId());
}
preparedStatement.setInt(++param, ownedAndUsedBy.getId());
}
if (maxArticlesPerCategory != Article.ALL_ARTICLES) {
preparedStatement.setInt(++param, maxArticlesPerCategory);
}
preparedStatement.setInt(++param, numberArticlesToReturn);
articles = Article.getArticles(preparedStatement,
true,
ownedAndUsedBy,
false,
con,
logger);
} finally {
preparedStatement.close();
}
return articles;
}
/**
* Return an array of article linked to the object with the specified detail
* type and id.
*
* This method does not exclude any categories
*
* If the detail type is for an article, articles linked to objects linked
* to that article are returned instead.
*
* @param detailType DetailType with which the id is linked
* @param linkId Id of the linked object
* @param numberArticlesToReturn Number of articles to return
* @param category Category with which articles are associated. If null
* articles are returned regardless of their category
* @param ownedAndUsedBy The site for which the article is being requested.
* This will be the same as the site that owns this
* article (a site cannot get a match-related article for
* any other site).
* @param liveArticlesOnly If true only live articles are returned, otherwise
* unlaunched articles are included
* @param highlightsOnly if true only articles with video highlights are
* returned
* @param con A <code>Connection</code> to use in the method.
* @param logger A <code>Logger</code> to use in the method.
*
* @return An array of articles or null if no articles are found.
*
* @exception SQLException if a database error occurs.
*/
public static Article[] getArticlesByDetailType(DetailType detailType,
int linkId,
int numberArticlesToReturn,
Category category,
Site ownedAndUsedBy,
boolean liveArticlesOnly,
boolean highlightsOnly,
Connection con,
Logger logger)
throws SQLException {
return getArticlesByDetailType(
detailType,
linkId,
numberArticlesToReturn,
category,
ownedAndUsedBy,
null,
liveArticlesOnly,
highlightsOnly,
con,
logger);
}
/**
* @param detailType
* @param linkId
* @param numberArticlesToReturn
* @param category
* @param ownedAndUsedBy
* @param syndicationPartners
* @param liveArticlesOnly
* @param highlightsOnly
* @param con
* @param logger
* @return
* @throws SQLException
*/
public static Article[] getArticlesByDetailType(DetailType detailType,
int linkId,
int numberArticlesToReturn,
Category category,
Site ownedAndUsedBy,
Collection<Site> syndicationPartners,
boolean liveArticlesOnly,
boolean highlightsOnly,
Connection con,
Logger logger)
throws SQLException {
return getArticlesByDetailType(
detailType,
linkId,
numberArticlesToReturn,
category,
Article.ALL_ARTICLES,
ownedAndUsedBy,
syndicationPartners,
liveArticlesOnly,
false,
highlightsOnly ? WITH_VIDEOS : ALL_ARTICLES,
con,
logger);
}
/**
* Return an array of article linked to the object with the specified detail
* type and id.
*
* If the detail type is for an article, articles linked to objects linked
* to that article are returned instead.
*
* Only articles whose categories are searchable are returned
*
* @param detailType DetailType with which the id is linked
* @param linkId Id of the linked object
* @param numberArticlesToReturn Number of articles to return
* @param category Category with which articles are associated. If null
* articles are returned regardless of their category
* @param maxArticlesPerCategory Restricts the number of articles
* returned for each distinct category. Set to
* Article.ALL_ARTICLES and this limit is ignored.
* @param ownedAndUsedBy The site for which the article is being requested.
* This will be the same as the site that owns this
* article (a site cannot get a match-related article for
* any other site).
* @param liveArticlesOnly If true only live articles are returned, otherwise
* unlaunched articles are included
* @param videoInclusion Determines whether articles with videos should be
* returned. This has one of three values:
* 1) WITH_VIDEOS only articles with videos are
* returned
* 2) WITHOUT_VIDEOS only articles without videos are
* returned
* 3) ALL_ARTICLES all articles regardless of whether
* they have videos
* @param con A <code>Connection</code> to use in the method.
* @param logger A <code>Logger</code> to use in the method.
*
* @return An array of articles or null if no articles are found.
*
* @exception SQLException if a database error occurs.
*/
public static Article[] getArticlesByDetailType(DetailType detailType,
int linkId,
int numberArticlesToReturn,
Category category,
int maxArticlesPerCategory,
Site ownedAndUsedBy,
boolean liveArticlesOnly,
boolean searchableArticlesOnly,
int videoInclusion,
Connection con,
Logger logger)
throws SQLException {
return getArticlesByDetailType(detailType, linkId, numberArticlesToReturn,
category,maxArticlesPerCategory,ownedAndUsedBy, null,
liveArticlesOnly, searchableArticlesOnly, videoInclusion,
con, logger);
}
/**
* This method is used to convert an integer array to comma seperated string
* with its position
*
* @param nums An integer array
* @return String comma seperated integer values with the position in the
* form of String
*/
public static String convertIntArrayToIndexedCommaSeperatedString(int[] nums){
int counter = 0;
StringBuffer linkBuffer = new StringBuffer("?," + (++counter));
if(nums.length > 1){
for(int i=1; i< nums.length; i++){
linkBuffer.append(",?,");
linkBuffer.append(++counter);
}
}
return linkBuffer.toString();
}
/**
* This method is used to convert an integer array to comma seperated string
*
* @param nums An integer array
* @return String comma seperated integer values in the form of String
*/
public static String convertIntArrayToCommaSeperatedString(int[] nums){
StringBuffer linkBuffer = new StringBuffer("?");
if(nums.length > 1){
for(int i=1; i< nums.length; i++){
linkBuffer.append(",?");
}
}
return linkBuffer.toString();
}
/**
* This method returns the articles associated with the link ids for
* a given category.The number of articles for each link id returned is
* the minimum of numberOfArticlesPerLinkId and actual number of articles
* associated with the link. The articles are returned in the same order
* as that of link ids supplied.
*
* @param detailTypeIds An array of detail type ids of the link ids.
* @param linkIds An array of link ids
* @param numberOfArticlesPerLinkId Number of articles to fetch for each
* link id
* @param categoryIds An array of category ids with which articles are associated.
* @param ownedAndUsedBy The site for which the article is being requested.
* @param liveArticlesOnly If true only live articles are returned, otherwise
* unlaunched articles are included
* @param syndicationPartners The {@link Collection} of {@link Site} details
* for syndication partners to include in the site fetching.
* @param dbConnection Data base connection
* @param logger Logger instance
*
* @return The articles associated with the link ids.
*
* @throws SQLException
*/
public static Article[] getArticlesFromLinkedIds(DetailType detailType,
int[] linkIds,
int numberOfArticlesPerLinkId,
int[] categoryIds,
Site ownedAndUsedBy,
boolean liveArticlesOnly,
Collection<Site> syndicationPartners,
Connection dbConnection,
Logger logger
)throws SQLException {
final boolean includeSyndication = (syndicationPartners != null) && !syndicationPartners.isEmpty();
//
//Query to retrive articles from the given
//detailTypeIds, linkIds and categoryIds
//
PreparedStatement preparedStatement = null;
Article[] articles = null;
StringBuffer buf = new StringBuffer();
buf.append("SELECT * ");
buf.append("FROM ");
buf.append("( ");
buf.append("SELECT ");
buf.append("links.link_id, ");
buf.append(Article.SQL_ARTICLE_COLUMNS);
buf.append(", ");
buf.append("RANK() OVER ");
buf.append("( ");
buf.append("PARTITION BY links.link_id ");
buf.append("ORDER BY a.article_date DESC ");
buf.append(") articlerank ");
buf.append("FROM editorial_articles a ,article_links links ");
buf.append("WHERE a.artl_id = links.artl_id ");
buf.append("AND (");
buf.append("links.detail_type_id = ? ");
buf.append(" AND links.link_id in ( ");
buf.append(convertIntArrayToIndexedCommaSeperatedString(linkIds));
buf.append(" ) ");
// Only check for categories if it has been passed through.
if (categoryIds != null && categoryIds.length != 0) {
buf.append(" AND a.catg_id in ( ");
buf.append(convertIntArrayToIndexedCommaSeperatedString(categoryIds));
buf.append(" ) ");
}
buf.append(" ) ");
buf.append((liveArticlesOnly ? Article.LIVE_ARTICLES_SQL : " "));
if(includeSyndication) {
buf.append("AND ((a.orgn_club_id = ?) OR (a.syndicated_flg='Y' AND a.orgn_club_id IN (");
buf.append(ConnectionPool.getTupleInList(syndicationPartners.size(), "?"));
buf.append("))) ");
} else {
buf.append("AND a.orgn_club_id = ? ");
}
buf.append(") ");
buf.append("WHERE articlerank <= ? ");
buf.append("ORDER BY decode(link_id, ");
buf.append(convertIntArrayToIndexedCommaSeperatedString(linkIds));
buf.append(" ) ");
String QUERY = buf.toString();
logger.debug("Query for fetching Articles from link ids : " + QUERY);
try {
preparedStatement = ConnectionPool.prepareStatement(QUERY,
dbConnection,
logger);
int param=0;
preparedStatement.setInt(++param, detailType.getId());
for (int ii = 0; ii < linkIds.length; ii++) {
//
// Loop for the link_id in ()...
//
preparedStatement.setInt(++param, linkIds[ii]);
}
if (categoryIds != null && categoryIds.length != 0) {
for (int ii = 0; ii < categoryIds.length; ii++) {
//
// Loop for category in ()...
//
preparedStatement.setInt(++param, categoryIds[ii]);
}
}
// Set the site ID and syndication partners (if supplied)
preparedStatement.setInt(++param, ownedAndUsedBy.getId());
if(includeSyndication) {
for(Site syndicationPartner: syndicationPartners) {
preparedStatement.setInt(++param, syndicationPartner.getId());
}
}
preparedStatement.setInt(++param, numberOfArticlesPerLinkId);
for (int ii = 0; ii < linkIds.length; ii++) {
//
// Loop for the decode
//
preparedStatement.setInt(++param, linkIds[ii]);
}
articles = Article.getArticles(preparedStatement,
true,
ownedAndUsedBy,
false,
dbConnection,
logger);
} finally {
preparedStatement.close();
}
return articles;
}
/**
* Return an article linked to the object with the specified detail
* type and id. If several articles are linked, the article with the most
* recent date is returned.
*
* @param detailType DetailType with which the id is linked
* @param linkId Id of the linked object
* @param numberArticlesToReturn Number of articles to return
* @param category Category with which articles are associated. If null
* articles are returned regardless of their category
* @param ownedAndUsedBy The site for which the article is being requested.
* This will be the same as the site that owns this
* article (a site cannot get a match-related article for
* any other site).
* @param liveArticlesOnly If true only live articles are returned, otherwise
* unlaunched articles are included
* @param highlightsOnly if true only articles with video highlights are
* returned
* @param con A <code>Connection</code> to use in the method.
* @param logger A <code>Logger</code> to use in the method.
*
* @return An articles or null if no articles are found.
*
* @exception SQLException if a database error occurs.
*/
public static Article getArticleByDetailType(DetailType detailType,
int linkId,
Category category,
Site ownedAndUsedBy,
boolean liveArticlesOnly,
Connection con,
Logger logger)
throws SQLException {
Article articles[] = Article.getArticlesByDetailType(detailType,
linkId,
1,
category,
ownedAndUsedBy,
liveArticlesOnly,
false,
con,
logger);
Article article = null;
if (null != articles) {
article = articles[0];
}
return article;
}
/**
* Gets image (header image and teaser image) for all <code>Articles</code>.
* passed in input LinkedHashMap. This input array
* will be updated by this method for each article which has image data
* associated with it. If more than one type of image is linked to an article
* the first image is used.
*
* @param articles A HashMap with article Id --> <code>Article</code>
* mapping.
* @param con A database connection.
* @param logger An error logger.
*
* @throws SQLException Exception due to the database error.
*/
protected static void getArticleImages(Article[] articles,
Connection con,
Logger logger)
throws SQLException {
//
// Populate a hashset containing the ids of all the images to retrieve.
//
HashSet<Integer> imageIds = new HashSet<Integer>();
for (int ii = 0; ii < articles.length; ++ii) {
Article article = articles[ii];
if (NO_IMAGE_ID != article.headerImageId) {
imageIds.add(new Integer(article.headerImageId));
}
if (NO_IMAGE_ID != article.teaserImageId) {
imageIds.add(new Integer(article.teaserImageId));
}
if (NO_IMAGE_ID != article.mobileImageId) {
imageIds.add(new Integer(article.mobileImageId));
}
if (NO_IMAGE_ID != article.videoHoldingImageId) {
imageIds.add(new Integer(article.videoHoldingImageId));
}
}
int imageCount = imageIds.size();
if (imageCount > 0) {
//
// Build an array containing all the id's of images to associated with
// the articles
//
int[] imageIdArray = new int[imageCount];
int ii = 0;
for (Iterator<Integer> imagesIterator = imageIds.iterator();
imagesIterator.hasNext();
++ii) {
imageIdArray[ii] = imagesIterator.next().intValue();
}
//
// Retrieve all the images
//
Image[] articleImages = Image.getImages(imageIdArray, con, logger);
//
// Associated the articles with their respective images.
//
HashMap<Integer, Image> imageIdsToImage = new HashMap<Integer, Image>();
for (int jj = 0; jj < articleImages.length; ++jj) {
imageIdsToImage.put(new Integer(articleImages[jj].getId()),
articleImages[jj]);
}
for (int kk = 0; kk < articles.length; ++kk) {
Article article = articles[kk];
if (NO_IMAGE_ID != article.headerImageId) {
article.headerImage = imageIdsToImage.get(new Integer(article.headerImageId));
}
if (NO_IMAGE_ID != article.teaserImageId) {
article.teaserImage = imageIdsToImage.get(new Integer(article.teaserImageId));
}
if (NO_IMAGE_ID != article.mobileImageId) {
article.mobileImage = imageIdsToImage.get(new Integer(article.mobileImageId));
}
if (NO_IMAGE_ID != article.videoHoldingImageId) {
article.videoHoldingImage = imageIdsToImage.get(new Integer(article.videoHoldingImageId));
}
}
}
}
/**
* Returns an array of <code>Articles</code> when given an array of
* <code>article IDs</code>. <p>
* Return array will only contain entries for articles where article was
* found on a database.<p>
* Articles returned will only contain article parts data, such as article
* headline, body, summary and teaser. <p>
* It returns null if no articles were found in the database for article IDs
* passed in to this method.
*
* Articles are returned in the same order as the id parameters
*
* @param articleId An array of ids to identify an array of
* <code>Articles</code>.
* @param liveArticlesOnly
* A boolean indicating whether only live articles or all
* <code>Articles</code> are required. If this flag is set
* to <code>true</code> then only live articles will be
* searched for.
* @param instantiateImages Whether to instantiate the images linked to the article
* @param usedBy The site that will be using the articles to be
* returned.
* @param con A <code>Connection</code> to use in the method.
* @param logger A <code>Logger</code> to use in the method.
*
* @return An array of articles corresponding to a the input array of article
* ids. Articles are returned in the same order as the id parameters.
*
* @exception SQLException if a database access error occurs
*/
public static Article[] getArticles(int[] articleIds,
boolean liveArticlesOnly,
boolean instantiateImages,
Site usedBy,
Connection con,
Logger logger)
throws SQLException {
Article[] articlesToReturn = null;
//
// Only query a database if an array of article id contains ids. Otherwise,
// return null.
//
if (0 != articleIds.length) {
//
// Convert the input array into a String of comma separated values so
// we can use them in a SQL query, enabling us to run this query only once
// in this case.
//
// Additionally build a decode statement used to ensure articles are
// returned in the same order as the id parameters
//
StringBuffer ids = new StringBuffer();
StringBuffer orderBy = new StringBuffer("decode(a.artl_id");
for (int ii = 0; ii < articleIds.length; ii++) {
ids.append((ii > 0 ? "," : "") + "?");
orderBy.append(",?," + (ii+1));
}
orderBy.append(", 9999)");
PreparedStatement preparedStatement = ConnectionPool.prepareStatement(
"SELECT " + Article.SQL_ARTICLE_COLUMNS +
(null != usedBy ?
", pr.seq_no as page_rank, " +
" hpr.seq_no as home_page_rank "
:
", null as page_rank, " +
" null as home_page_rank "
) +
"FROM editorial_articles a " +
(null != usedBy ?
", ranks pr, " +
" ranks hpr " : ""
) +
"WHERE a.artl_id IN (" + ids + ") " +
(liveArticlesOnly ? LIVE_ARTICLES_SQL : "") +
(null != usedBy ?
"AND a.artl_id = pr.artl_id (+)" +
"AND NVL(pr.rnkt_cd(+), '" + CATEGORY_RANK_TYPE +"') = '" +
CATEGORY_RANK_TYPE +"' " +
"AND NVL(pr.orgn_id(+), ?) = ? " +
"AND a.artl_id = hpr.artl_id (+)" +
"AND NVL(hpr.rnkt_cd(+), '" + HOME_CATEGORY_RANK_TYPE +"') = '" +
HOME_CATEGORY_RANK_TYPE +"' " +
"AND NVL(hpr.orgn_id(+), ?) = ? " +
"AND (a.orgn_club_id = ? OR general_flg = 'Y')" : ""
) +
"ORDER BY " + orderBy,
con,
logger
);
try {
//
// Set parameters to be passed to the above SQL query.
//
int param = 0;
for (int ii = 0; ii < articleIds.length; ii++) {
//
// First pass for the IN ()...
// Parameter values for prepared statement start from 1, hence we
// are adding 1 to ii counter.
//
preparedStatement.setInt(++param, articleIds[ii]);
}
if (null != usedBy) {
preparedStatement.setInt(++param, usedBy.getId());
preparedStatement.setInt(++param, usedBy.getId());
preparedStatement.setInt(++param, usedBy.getId());
preparedStatement.setInt(++param, usedBy.getId());
preparedStatement.setInt(++param, usedBy.getId());
}
for (int ii = 0; ii < articleIds.length; ii++) {
//
// Second pass for the DECODE in the ORDER BY
// Parameter values for prepared statement start from 1, hence we
// are adding 1 to ii counter.
//
preparedStatement.setInt(++param, articleIds[ii]);
}
articlesToReturn = Article.getArticles(preparedStatement,
instantiateImages,
usedBy,
true,
con,
logger);
} finally {
preparedStatement.close();
}
}
return articlesToReturn;
}
/**
* Returns an array of <code>Articles</code> that need to be either launched
* or expired. Note that this method only scans back a week and any articles
* outside of that range will be ignored for performance reasons.
*
* @param con A <code>Connection</code> to use in the method.
* @param logger A <code>Logger</code> to use in the method.
*
* @return An array of articles whose article date or site removed date has
* passed and which have not been launched or expired.
*
* @exception SQLException if a database access error occurs
*/
public static Article[] getArticlesForLaunchAndExpiry(Connection con,
Logger logger)
throws SQLException {
Article[] articlesToReturn = null;
//
// Query to return articles that should be launched or expired.
//
PreparedStatement query = ConnectionPool.prepareStatement(
"SELECT " + Article.SQL_ARTICLE_COLUMNS +
"FROM editorial_articles a " +
"WHERE a.artl_id IN ( " +
"SELECT artl_id " +
"FROM editorial_articles " +
"WHERE ( " +
"a.article_date > (SYSDATE - 7) " +
"AND SYSDATE >= a.article_date " +
"AND SYSDATE >= NVL(a.site_posted_date, " +
"TO_DATE('01-Jan-3001','DD-Mon-YYYY')) " +
"AND launched_flg = 'N' " +
"AND expired_flg = 'N' " +
") OR ( " +
"a.site_removed_date > (SYSDATE - 7) " +
"AND SYSDATE > NVL(a.site_removed_date, " +
"TO_DATE('01-Jan-3001','DD-Mon-YYYY')) " +
"AND expired_flg = 'N' " +
") " +
") ",
con,
logger
);
try {
//
// Call getArticles with completedArticle flag set to false as we are
// only interested in article parts data, not its images.
//
articlesToReturn = Article.getArticles(query,
false,
null,
false,
con,
logger);
} finally {
query.close();
}
return articlesToReturn;
}
/**
* Returns an array of Articles for the desktop alert application.
*
* @param usedBy Site for which Articles are to be returned. This is
* ignored if articles are retrieved across all clubs.
* @param alertType determines what articles are returned.
* Article.ALERTS_FOR_THIS_SITE
* Only articles with the desktop alert flag set true
* and which belong to the specified site are returned
* - shared articles are excluded.
* Article.ALERTS_FOR_ALL_CLUBS
* Only Articles with the desktop alert flag set true
* are returned, regardless of which site they belong
* @param numberOfArticles Maximum number of articles to return.
* @param dbConnection A <code>Connection</code> to use in the method.
* @param logger A <code>Logger</code> to use in the method.
*
* @return An array of articles which have been set to appear on the desktop
* alert application.
*
* @exception SQLException if a database access error occurs
*/
public static Article[] getArticlesForDesktopAlerts(Site usedBy,
int alertType,
int numberOfArticles,
Connection dbConnection,
Logger logger)
throws SQLException {
Article[] articlesToReturn = null;
//
// One of the valid alert types must be specified.
//
assert (Article.ALERTS_FOR_THIS_SITE == alertType ||
Article.ALERTS_FOR_ALL_CLUBS == alertType);
//
// Query to return articles that have been flagged for newsletters.
// Only live articles may be included.
//
PreparedStatement query = ConnectionPool.prepareStatement(
"SELECT " + SQL_ARTICLE_COLUMNS +
"FROM editorial_articles a " +
"WHERE artl_id IN ( " +
"SELECT artl_id FROM ( " +
"SELECT artl_id " +
"FROM editorial_articles " +
"WHERE desktop_alert_flg = 'Y'" +
(Article.ALERTS_FOR_THIS_SITE == alertType ? "AND orgn_club_id = ? " :
"AND general_flg = 'Y' ") +
"AND SYSDATE >= article_date " +
"AND SYSDATE >= NVL(site_posted_date, " +
"TO_DATE('01-Jan-3001', 'DD-Mon-YYYY')) " +
"AND SYSDATE < NVL(site_removed_date, " +
"TO_DATE('01-Jan-3001', 'DD-Mon-YYYY')) " +
"ORDER BY article_date DESC " +
") WHERE ROWNUM < ? " +
") ORDER BY article_date DESC ",
dbConnection,
logger
);
try {
int param = 0;
if (Article.ALERTS_FOR_THIS_SITE == alertType) {
//
// If alerts are for a particular site the site must be specified.
//
assert (null != usedBy);
query.setInt(++param, usedBy.getId());
}
query.setInt(++param, numberOfArticles);
//
// Call getArticles with completedArticle flag set to false as we are
// only interested in article parts data, not its images.
//
articlesToReturn = Article.getArticles(query,
true,
usedBy,
false,
dbConnection,
logger);
} finally {
query.close();
}
return articlesToReturn;
}
/**
* Returns a list of <code>Articles</code> in a given set of categories and
* written between two specified dates. Only articles usable by the specified
* site are returned (ie articles written by this site or sharable articles).
* <p>
* It returns null if no articles were found in the database for specified
* week and specified categories.
*
* @param startDate A start date for which articles are to be included.
* @param endDate A end date for which articles are to be included.
* @param categories An array of <code>Categories</code> for which
* <code>Articles</code> should be included. May be null,
* in which case no articles are returned.
* @param usedBy A <code>Site</code> for which articles are being
* retrieved. Only sharable articles or ones written by
* this site will be returned.
* @param postedNonRemovedArticlesOnly
* A boolean indicating whether only articles which have
* been posted and not removed or all
* <code>Articles</code> are required. If this flag is set
* to <code>true</code> then only posted and non-removed
* articles will be searched for.
* @param generalArticles
* A boolean indicating whether general articles should be
* included in the return array. If this flag is set to
* <code>true</code> then site's and shared articles will
* be included.
* @param includeFutureArticles
* True if articles with an article date in the future
* should be searched for, false otherwise.
* @param con A <code>Connection</code> to use in the method.
* @param logger A <code>Logger</code> to use in the method.
*
* @return An array of articles corresponding to a the input array of article
* ids.
*
* @exception SQLException if a database access error occurs
*/
public static Article[] getArticles(java.util.Date startDate,
java.util.Date endDate,
Category[] categories,
Site usedBy,
boolean sortFlag,
Connection con,
Logger logger)
throws SQLException {
logger.log(
Level.DEBUG,
"Trying to get an array of articles for an array of " +
"categories and the specified week. Start date is " +
startDate.toString() + " and end date is " + endDate.toString()
);
Article[] articlesToReturn = null;
//
// Only query a database if an array of categories is not null. Otherwise,
// return null.
//
if (null != categories) {
//
// Convert the input array into a String of comma separated values so
// we can use them in a SQL query, enabling us to run this query only once
// in this case.
//
StringBuffer ids = new StringBuffer("?");
for (int ii = 0; ii < categories.length - 1; ii++) {
ids.append(", ?");
}
//default sorting if not requested
String orderArticles = " ORDER BY article_date DESC";
if(sortFlag){
orderArticles=" ORDER BY article_date ASC";
}
//
// Use >= and < to check for article date instead of BETWEEN as we want
// to include articles written on start dates too.
//
// Returned data is ordered by the date, with the latest articles being
// on the top of the list.
//
String queryString = String.format(
FMT_SELECT_FOR_LISTING_BY_LINKED_AND_POSTED_DATE_MULTI_CATEGORY+orderArticles, ids
.toString());
PreparedStatement pStmt = ConnectionPool.prepareStatement(queryString,
con,
logger
);
try {
//
// Set parameters to be passed to the above SQL query.
//
int param = 0;
pStmt.setInt(++param, PageElement.DETAIL_TYPE_DATE_ID);
pStmt.setDate(++param, new java.sql.Date(startDate.getTime()));
pStmt.setDate(++param, new java.sql.Date(endDate.getTime()));
pStmt.setInt(++param, usedBy.getId());
for (int i = 0; i < categories.length; i++) {
pStmt.setInt(++param, categories[i].getCategoryId());
}
//
// Call getArticles with completedArticle flag set to false as we are
// only interested in article parts data, not its images.
//
articlesToReturn = Article.getArticles(pStmt, true, usedBy, false, con, logger);
} finally {
pStmt.close();
}
}
if (!ArrayUtils.isEmpty(articlesToReturn)) {
for (int i=0; i < articlesToReturn.length; i++) {
articlesToReturn[i].getRelatedArticleKeywords(con, logger);
}
}
return articlesToReturn;
}
/**
* Returns an array of <code>Article</code> for a given <code>Category</code>.
* If no articles are found, returns NULL.<p>
* Returned articles will be ordered by category or article date depending on
* orderByHeadline flag setting.<p>
* Only articles usable by the specified site are returned (ie articles
* written by this site or sharable articles).
*
* @param category The Category for which a list of articles is requested.
* If no category is specified then articles with the
* homePage flag set are returned.
* Note that if no category is specified, the site must be
* present.
* @param ascendingOrder
* The order in which the Results are returned. If this is
* set to <code>true</code> order will be asceding,
* and desceding if this is set to <code>false</code>.
* @param instantiateImages If true images linked to the article are
* instantiated.
* @param liveArticlesOnly
* A boolean indicating whether only live articles or all
* <code>Articles</code> are required. If this flag is set
* to <code>true</code> then only live articles will be
* searched for.
* @param syndicatedArticlesOnly
* It true will only get articles with the syndicated flat
* set to yes.
* @param noOfArticles The number of Articles that are returned.
* @param usedBy The site for which articles are being requested. If
* no site is specified articles are returned for all
* sites - regardless of whether the general_flg is set
* or not.
*
* Note that if no site is specified, the category must be
* present.
*
* @param con A <code>Connection</code> to use in the method.
* @param logger A <code>Logger</code> to use in the method.
*
* @return The <code>Article</code> objects requested, or null if not found.
*
* @exception SQLException if a database access error occurs
*/
public static Article[] getArticles(Category category,
boolean ascendingOrder,
boolean instantiateImages,
boolean liveArticlesOnly,
boolean syndicatedArticlesOnly,
int noOfArticles,
Site usedBy,
Connection con,
Logger logger)
throws SQLException {
//
// Either a site or a category must be specified. Otherwise the query
// will take forever.
//
assert (null != category || null != usedBy);
logger.log(
Level.DEBUG,
"Get articles for a category " +
(null != category ? category.getCategoryId() : -1) +
". We need " + noOfArticles + " articles"
);
PreparedStatement preparedStatement = ConnectionPool.prepareStatement(
"SELECT " + Article.SQL_ARTICLE_COLUMNS +
"FROM editorial_articles a " +
"WHERE a.artl_id IN (" +
"SELECT artl_id FROM (" +
"SELECT a1.artl_id " +
"FROM editorial_articles a1 " +
"WHERE " +
(null == category ? "hmpg_flg = 'Y' " : "catg_id = ? ") +
(null != usedBy ?
"AND (orgn_club_id = ? OR general_flg = 'Y') " : "") +
(syndicatedArticlesOnly?
" AND a1.syndicated_flg = 'Y' ":"") +
(liveArticlesOnly ?
"AND SYSDATE >= a1.article_date " +
"AND SYSDATE >= NVL(a1.site_posted_date, " +
"TO_DATE('01-Jan-3001', 'DD-Mon-YYYY')) " +
"AND SYSDATE < NVL(a1.site_removed_date, " +
"TO_DATE('01-Jan-3001', 'DD-Mon-YYYY')) "
: ""
) +
"ORDER BY article_date " + (ascendingOrder ? "ASC" : "DESC") +
") " +
(Article.ALL_ARTICLES != noOfArticles ? "WHERE rownum <= ? " : "") +
") " +
"ORDER BY article_date " + (ascendingOrder ? "ASC" : "DESC"),
con,
logger
);
Article[] articleToReturn;
try {
int param = 0;
if (null != category) {
preparedStatement.setInt(++param, category.getCategoryId());
}
if (null != usedBy) {
preparedStatement.setInt(++param, usedBy.getId());
}
if (Article.ALL_ARTICLES != noOfArticles) {
preparedStatement.setInt(++param, noOfArticles);
}
//
// Construct Article objects based on the above SQL
//
articleToReturn = Article.getArticles(
preparedStatement,
instantiateImages,
usedBy,
false,
con,
logger
);
} finally {
preparedStatement.close();
}
return articleToReturn;
}
/**
* Returns an array of <code>Article</code> which contain the specified
* text.
*
*
* @param text Text to search for. Not that this conforms to the Oracle
* text spec and can contain boolean operators such as "and", not", +,
* - etc
* @param if not null only articles in the array of categories are included
* in the search results
* @param startDate if not null only articles dated after this date are
* included in the results
* @param endDate if not null only articles dated before this date are
* included in the results
* @param numberToReturn Maximum number of articles the method will return.
* Fewer may be returned if there's not enought matching data
* instantiated.
* @param startFromPosition Used for paginated results. For example, if 100
* articles match the search string, you can retrieve the latter 50
* by setting this field to 50.
* @param liveArticlesOnly If true only launched articles which have not been
* expired will be returned.
* @param instantiateImages If true images associated with the article
* will be instantiated
* @param site Only articles belonging to the specified site are returned
* @param dbConnection Database connection
* @param logger Logger
*
* @return An array of articles or null if no articles were found.
*
* @exception SQLException if a database access error occurs
*/
public static Article[] getArticlesContaining(String text,
Category[] categories,
Date startDate,
Date endDate,
int numberToReturn,
int startFromPosition,
boolean liveArticlesOnly,
boolean instantiateImages,
Site site,
Connection dbConnection,
Logger logger)
throws SQLException {
return Article.getArticlesContaining(
text,
Article.ALL_ARTICLE_PARTS,
categories,
startDate,
endDate,
numberToReturn,
startFromPosition,
liveArticlesOnly,
instantiateImages,
site,
dbConnection,
logger
);
}
/**
* Returns an array of <code>Article</code> which contain the specified
* text.
*
*
* @param text Text to search for. Not that this conforms to the Oracle
* text spec and can contain boolean operators such as "and", not", +,
* - etc
* @param articleParts String array denoting which parts of the article to
* search (i.e. "headline", "teaser", "body", "keywords")
* @param if not null only articles in the array of categories are included
* in the search results
* @param startDate if not null only articles dated after this date are
* included in the results
* @param endDate if not null only articles dated before this date are
* included in the results
* @param numberToReturn Maximum number of articles the method will return.
* Fewer may be returned if there's not enought matching data
* instantiated.
* @param startFromPosition Used for paginated results. For example, if 100
* articles match the search string, you can retrieve the latter 50
* by setting this field to 50.
* @param liveArticlesOnly If true only launched articles which have not been
* expired will be returned.
* @param instantiateImages If true images associated with the article
* will be instantiated
* @param site Only articles belonging to the specified site are returned
* @param dbConnection Database connection
* @param logger Logger
*
* @return An array of articles or null if no articles were found.
*
* @exception SQLException if a database access error occurs
*/
public static Article[] getArticlesContaining(String text,
String[] articleParts,
Category[] categories,
Date startDate,
Date endDate,
int numberToReturn,
int startFromPosition,
boolean liveArticlesOnly,
boolean instantiateImages,
Site site,
Connection dbConnection,
Logger logger)
throws SQLException {
Article[] articles = null;
CachedTypedProperties articleProperties = null;
int searchTextLength = OracleSQLFilter.UNLIMITED_LENGTH;
int searchTextWordCount = OracleSQLFilter.UNLIMITED_WORD_COUNT;
try {
articleProperties = CachedTypedProperties.getInstance(ARTICLE_PROPERTIES_FILE_NAME);
searchTextLength = articleProperties.getIntProperty("search.text.length");
searchTextWordCount = articleProperties.getIntProperty("search.text.word.count");
} catch (ClassNotFoundException e) {
logger.error(ARTICLE_PROPERTIES_FILE_NAME + " could not be found", e);
}
OracleSQLFilter filter = new OracleSQLFilter(searchTextLength, searchTextWordCount, text);
text = filter.filterString();
if (!StringUtils.isNullOrEmpty(text)) {
HashSet<String> parts = new HashSet<String>();
for (int ii = 0; ii < articleParts.length; ++ii) {
if (!ALL_ARTICLE_PARTS_SET.contains(articleParts[ii])) {
throw new RuntimeException(
"Invalid article part specified: " + articleParts[ii]
);
} else {
parts.add(articleParts[ii]);
}
}
//
// Create category IDs list
//
StringBuffer categoryIds = null;
if (categories != null && categories.length > 0) {
categoryIds = new StringBuffer();
for (int ii = 0; ii < categories.length; ii++) {
categoryIds.append((ii == 0 ? "" : ",") + "?");
}
}
String sql =
"SELECT " + Article.SQL_ARTICLE_COLUMNS +
"FROM editorial_articles a WHERE artl_id IN ( " +
"SELECT artl_id FROM ( " +
"SELECT artl_id, article_date, headline, rownum rnum FROM ( ";
for (int ii = 0; ii < articleParts.length; ++ii) {
sql += (ii > 0 ? " UNION " : "");
if (!articleParts[ii].equals(Article.ARTICLE_PART_KEYWORDS)) {
sql += "SELECT artl_id, article_date, headline " +
"FROM editorial_articles " +
"WHERE CONTAINS (" + articleParts[ii] + ", ?) > 0 " +
"AND orgn_club_id = ? ";
} else {
sql += "SELECT artl_id, article_date, headline " +
"FROM editorial_articles " +
"WHERE artl_id IN (" +
"SELECT artl_id FROM article_keywords " +
"WHERE keyword = ? " +
"AND orgn_id = ? " +
") ";
}
sql += (startDate != null ? " AND article_date >= ? " : "" ) +
(endDate != null ? " AND article_date < ? " : "" ) +
(null != categoryIds ? " AND catg_id IN (" + categoryIds +") "
: "") +
(liveArticlesOnly ?
"AND SYSDATE >= article_date " +
"AND SYSDATE >= NVL(site_posted_date, " +
"TO_DATE('01-Jan-3001', 'DD-Mon-YYYY')) " +
"AND SYSDATE < NVL(site_removed_date, " +
"TO_DATE('01-Jan-3001', 'DD-Mon-YYYY')) "
: ""
);
}
sql+= "ORDER BY article_date DESC, headline ASC " +
") " +
") WHERE rnum >= ? AND rnum < ? " +
") ORDER BY article_date DESC, headline ASC";
PreparedStatement query = ConnectionPool.prepareStatement(
sql,
dbConnection,
logger
);
try {
int param = 0;
//
// Set nearly identical parameters for the 4 different queries which
// are UNIONED together
//
for (int ii = 0; ii < articleParts.length; ii++) {
query.setString(++param, text);
query.setInt(++param, site.getId());
if (startDate != null) {
query.setDate(++param, new java.sql.Date(startDate.getTime()) );
}
if (endDate != null) {
query.setDate(++param, new java.sql.Date(endDate.getTime()) );
}
if (categories != null) {
for (int jj = 0; jj < categories.length; ++jj) {
query.setInt(++param, categories[jj].getCategoryId());
}
}
}
query.setInt(++param, startFromPosition);
query.setInt(++param, startFromPosition + numberToReturn);
//
// Construct Article objects based on the above SQL
//
articles = Article.getArticles(
query,
instantiateImages,
site,
false,
dbConnection,
logger
);
} finally {
query.close();
}
}
return articles;
}
/**
* Returns an <code>int</code> specifying the number of articles that match the
* criteria
*
* @param text Text to search for. Not that this conforms to the Oracle
* text spec and can contain boolean operators such as "and", not", +,
*
* @param articleParts String array denoting which parts of the article to
* search (i.e. "headline", "teaser", "body", "keywords")
* @param numberToReturn Maximum number of articles the method will return.
* Fewer may be returned if there's not enought matching data
* instantiated.
* @param startFromPosition Used for paginated results. For example, if 100
* articles match the search string, you can retrieve the latter 50
* by setting this field to 50.
* @param searchHeadlinesOnly If true only headlines will be searched.
* @param liveArticlesOnly If true only launched articles which have not been
* expired will be returned.
* @param instantiateImages If true images associated with the article
* will be instantiated
* @param site Only articles belonging to the specified site are returned
* @param dbConnection Database connection
* @param logger Logger
*
* @return An array of articles or null if no articles were found.
*
* @exception SQLException if a database access error occurs
*/
public static int getNumberOfArticlesContaining(String text,
Category[] categories,
Date startDate,
Date endDate,
boolean liveArticlesOnly,
Site site,
Connection dbConnection,
Logger logger)
throws SQLException {
return Article.getNumberOfArticlesContaining(
text,
Article.ALL_ARTICLE_PARTS,
categories,
startDate,
endDate,
liveArticlesOnly,
site,
dbConnection,
logger
);
}
/**
* Returns an <code>int</code> specifying the number of articles that match the
* criteria
*
*
* @param text Text to search for. Not that this conforms to the Oracle
* text spec and can contain boolean operators such as "and", not", +,
*
* @param articleParts String array denoting which parts of the article to
* search (i.e. "headline", "teaser", "body", "keywords")
* @param categories Restrict count to only those categories in this array
* @param startDate Only articles dated after this time are counted
* @param endDate Only articles dated before this tiem around counted
* @param liveArticlesOnly If true only launched articles which have not been
* expired will be returned.
* @param site Only articles belonging to the specified site are returned
* @param dbConnection Database connection
* @param logger Logger
*
* @return An array of articles or null if no articles were found.
*
* @exception SQLException if a database access error occurs
*/
public static int getNumberOfArticlesContaining(String text,
String[] articleParts,
Category[] categories,
Date startDate,
Date endDate,
boolean liveArticlesOnly,
Site site,
Connection dbConnection,
Logger logger)
throws SQLException {
int count = 0;
CachedTypedProperties articleProperties = null;
int searchTextLength = OracleSQLFilter.UNLIMITED_LENGTH;
int searchTextWordCount = OracleSQLFilter.UNLIMITED_WORD_COUNT;
try {
articleProperties = CachedTypedProperties.getInstance(ARTICLE_PROPERTIES_FILE_NAME);
searchTextLength = articleProperties.getIntProperty("search.text.length");
searchTextWordCount = articleProperties.getIntProperty("search.text.word.count");
} catch (ClassNotFoundException e) {
logger.error(ARTICLE_PROPERTIES_FILE_NAME + " could not be found", e);
}
OracleSQLFilter filter = new OracleSQLFilter(searchTextLength, searchTextWordCount, text);
text = filter.filterString();
if (!StringUtils.isNullOrEmpty(text)) {
HashSet<String> parts = new HashSet<String>();
for (int ii = 0; ii < articleParts.length; ++ii) {
if (!ALL_ARTICLE_PARTS_SET.contains(articleParts[ii])) {
throw new RuntimeException(
"Invalid article part specified: " + articleParts[ii]
);
} else {
parts.add(articleParts[ii]);
}
}
//
// Create category IDs list
//
StringBuffer categoryIds = null;
if (categories != null && categories.length > 0) {
categoryIds = new StringBuffer();
for (int ii = 0; ii < categories.length; ii++) {
categoryIds.append((ii == 0 ? "" : ",") + "?");
}
}
String sql = "SELECT count(*) as count FROM ( ";
for (int ii = 0; ii < articleParts.length; ++ii) {
sql += (ii > 0 ? " UNION " : "");
if (!articleParts[ii].equals(Article.ARTICLE_PART_KEYWORDS)) {
sql += "SELECT artl_id " +
"FROM editorial_articles " +
"WHERE CONTAINS (" + articleParts[ii] + ", ?) > 0 " +
"AND orgn_club_id = ? ";
} else {
sql += "SELECT artl_id " +
"FROM editorial_articles " +
"WHERE artl_id IN (" +
"SELECT artl_id FROM article_keywords " +
"WHERE keyword = ? " +
"AND orgn_id = ? " +
") ";
}
sql += (startDate != null ? " AND article_date >= ? " : "" ) +
(endDate != null ? " AND article_date < ? " : "" ) +
(null != categoryIds ? " AND catg_id IN (" + categoryIds +") "
: "") +
(liveArticlesOnly ?
"AND SYSDATE >= article_date " +
"AND SYSDATE >= NVL(site_posted_date, " +
"TO_DATE('01-Jan-3001', 'DD-Mon-YYYY')) " +
"AND SYSDATE < NVL(site_removed_date, " +
"TO_DATE('01-Jan-3001', 'DD-Mon-YYYY')) "
: ""
);
}
sql+= ") ";
PreparedStatement query = ConnectionPool.prepareStatement(
sql,
dbConnection,
logger
);
try {
int param = 0;
//
// Set nearly identical parameters for the 4 different queries which
// are UNIONED together
//
for (int ii = 0; ii < articleParts.length; ii++) {
query.setString(++param, text);
query.setInt(++param, site.getId());
if (startDate != null) {
query.setDate(++param, new java.sql.Date(startDate.getTime()) );
}
if (endDate != null) {
query.setDate(++param, new java.sql.Date(endDate.getTime()) );
}
if (categories != null) {
for (int jj = 0; jj < categories.length; ++jj) {
query.setInt(++param, categories[jj].getCategoryId());
}
}
}
ResultSet rs = ConnectionPool.executeQuery(query, dbConnection, logger);
if (rs.next()) {
count = rs.getInt("count");
}
} finally {
query.close();
}
}
return count;
}
/**
* Returns a <code>String</code> containing the SQL to retrieve Multicategory
* Syndication
*
* @param showUnsyndicatedArticles include non-syndicated articles as well
*
* @param syndicationCategories List of categories to which the syndicated
* articles have to be associated to.
*
* @param syndicationPartners List of sites by which the syndicated articles
* have to be owned by.
*
*@return A SQL String.
*
*/
private static String getMulticategorySyndicationSQL(boolean showUnsyndicatedArticles,
Category[] syndicationCategories,
Site[] syndicationPartners) {
StringBuffer buf = new StringBuffer();
buf.append(" ( ");
if(!showUnsyndicatedArticles) {
buf.append(" a.syndicated_flg = 'Y' ");
}
if (syndicationCategories != null && syndicationCategories.length > 0) {
if(!showUnsyndicatedArticles) {
buf.append(" AND ");
}
buf.append(" a.catg_id in (");
for (int i=0; i<syndicationCategories.length-1; i++) {
buf.append("?,");
}
buf.append("?) ");
}
if(!showUnsyndicatedArticles || (syndicationCategories != null && syndicationCategories.length > 0)) {
buf.append(" AND ");
}
buf.append(" a.orgn_club_id in (");
for (int i=0; i<syndicationPartners.length-1; i++) {
buf.append("?,");
}
buf.append("?)) ");
return buf.toString();
}
/**
* Returns a <code>String</code> containing the SQL Club and Category text
*
* @param site Site by which the non syndicated articles have to be
* owned by.
* @param includeSyndicatedArticles if true will add SQL Logic to include syndicated
* Articles
*
* @return A SQL String
*/
private static String getMulticategoryClubAndCategorySQL(Page[] pages, Site site,
boolean includeSyndicatedArticles, String tablePrefix) {
if(tablePrefix != null && tablePrefix.length() > 0) {
tablePrefix += ".";
} else {
tablePrefix = "";
}
StringBuffer buf = new StringBuffer();
if (pages != null && pages.length > 0) {
buf.append(" (("+ tablePrefix + "orgn_club_id = ?");
buf.append(" OR "+ tablePrefix + "general_flg = 'Y') AND "+ tablePrefix + "catg_id IN (");
for (int i=0; i<pages.length-1; i++) {
if (pages[i].getCategory() != null) {
buf.append("?,");
}
}
if (pages[pages.length-1].getCategory() != null) {
buf.append("?");
}
else {
// Remove the last comma
buf.delete(buf.length()-1, buf.length());
}
buf.append(")) ");
if (includeSyndicatedArticles) {
buf.append(" OR ");
}
}
return buf.toString();
}
/**
* Returns the number of articles found in the database for the specified
* site and pages and also for the specified list of syndicated partners
* and syndicated categories.
*
* @param site Site by which the non syndicated articles have to be
* owned by.
*
* @param pages The pages whose category the non syndicated articles
* have to be associated to.
* @param syndicationPartners List of sites by which the syndicated articles
* have to be owned by. *
* @param syndicationCategories List of categories to which the syndicated
* articles have to be associated to.
* @param allSyndicatedCategories Whether to all syndicated articles from
* the syndicated partners independently of
* to which category they belong. If this
* flag is set to true the list of categories
* in the syndicationCategories parameter will
* be ignored.
* @param liveArticlesOnly If true only live articles are counted
* @param videoInclusion Determines whether articles with videos should be
* counted. This has one of three values:
* 1) WITH_VIDEOS only articles with videos are
* counted
* 2) WITHOUT_VIDEOS only articles without videos are
* counted
* 3) ALL_ARTICLES all articles regardless of whether
* they have videos are counted
*
* @param showUnsyndicatedArticles whether to include unsyndicated articles as well
*
* @param dbConnection A <code>Connection</code> to use in the method.
* @param logger A <code>Logger</code> to use in the method.
*
* @return the number of articles found in the database for the specified
* site and pages and also for the specified list of syndicated partners
* and syndicated categories.
*
* @exception SQLException if a database access error occurs
*/
public static int getMulticategoryNumberOfArticles(Site site,
Page[] pages,
Site[] syndicationPartners,
Category[] syndicationCategories,
boolean allSyndicatedCategories,
boolean liveArticlesOnly,
int videoInclusion,
boolean showUnsyndicatedArticles,
Connection dbConnection,
Logger logger) throws SQLException {
int numArticles = 0;
PreparedStatement ps = null;
String query =
"SELECT count(*) AS count " +
"FROM editorial_articles a " +
"WHERE ( ";
boolean includeSyndicatedArticles =getIncludeSyndicatedArticles(syndicationPartners,
allSyndicatedCategories, syndicationCategories);
query += getMulticategoryClubAndCategorySQL(pages, site, includeSyndicatedArticles, null);
if (includeSyndicatedArticles) {
query += getMulticategorySyndicationSQL(showUnsyndicatedArticles, syndicationCategories, syndicationPartners);
}
query += " ) " + (liveArticlesOnly ? LIVE_ARTICLES_SQL : "") +
Article.getVideoInclusionSQL(videoInclusion, "a.");
try {
ps = ConnectionPool.prepareStatement(
query,
dbConnection,
logger
);
int param = 0;
if (pages != null && pages.length > 0) {
ps.setInt(++param, site.getId());
for (int p=0; p<pages.length; p++) {
if (pages[p].getCategory() != null) {
ps.setInt(++param, pages[p].getCategory().getCategoryId());
}
}
}
if (includeSyndicatedArticles) {
for (int sc=0; sc<syndicationCategories.length; sc++) {
ps.setInt(++param, syndicationCategories[sc].getCategoryId());
}
for (int sp=0; sp<syndicationPartners.length; sp++) {
ps.setInt(++param, syndicationPartners[sp].getId());
}
}
ResultSet rs = ConnectionPool.executeQuery(ps, dbConnection, logger);
if (rs.next()) {
numArticles = rs.getInt("count");
}
} finally {
ps.close();
}
return numArticles;
}
/**
* Returns a <code>String</code> object containing SQL to retrieve
* the specified site and pages and also for the specified list of
* syndicated partners and syndicated categories. It also takes in an
* integer defining how many articles to be returned by this method
* and in what position start (for pagination purposes). The articles
* returned will be order by Rating. The articles also have the rating populated.
*
* @param site Site by which the non syndicated articles have to be
* owned by.
*
* @param pages The pages whose category the non syndicated articles
* have to be associated to.
* @param syndicationPartners List of sites by which the syndicated articles
* have to be owned by.
* @param syndicationCategories List of categories to which the syndicated
* articles have to be associated to.
* @param allSyndicatedCategories Whether to all syndicated articles from
* the syndicated partners independently of
* to which category they belong. If this
* flag is set to true the list of categories
* in the syndicationCategories parameter will
* be ignored.
* @param startFromPosition Allows the article index to be offset from the
* start. For example, if this is set to 2, the method
* will still return the specified maximum number of
* articles, but will skip over the article which would
* otherwise have been first.
* @param maxNumArticles The maximum number of articles to return.
* @param liveArticlesOnly If true only live articles are counted
* @param videoInclusion Determines whether articles with videos should be
* returned. This has one of three values:
* 1) WITH_VIDEOS only articles with videos are
* returned
* 2) WITHOUT_VIDEOS only articles without videos are
* returned
* 3) ALL_ARTICLES all articles regardless of whether
* they have videos are returned
*
* @param showUnsyndicatedArticles whether to include unsyndicated articles as well
* @param logger A <code>Logger</code> to use in the method.
*
* @return A SQL String to retrieve Multicategor yArticles Ordered By Rating.
*/
private static String getMulticategoryArticlesByRatingSQL(Site site,
Page[] pages,
Site[] syndicationPartners,
Category[] syndicationCategories,
boolean allSyndicatedCategories,
boolean includeSyndicatedArticles,
int startFromPosition,
int maxNumArticles,
boolean liveArticlesOnly,
int videoInclusion,
boolean showUnsyndicatedArticles,
Logger logger) {
StringBuffer buf = new StringBuffer();
buf.append("SELECT * FROM ( ");
buf.append("SELECT rownum as rnum, ");
buf.append(SQL_ARTICLE_COLUMNS);
buf.append("FROM ( ");
buf.append("SELECT ");
buf.append(SQL_ARTICLE_COLUMNS);
buf.append(", nvl((r.TOTAL_ACTUAL_RATING/r.NUMBER_OF_USERS_RATED),");
buf.append(site.getRatingsConfig().getDefaultRatingValue());
buf.append(") as ratingValue ");
buf.append("FROM editorial_articles a, Rating r ");
buf.append("WHERE ( ");
buf.append(getMulticategoryClubAndCategorySQL(pages, site, includeSyndicatedArticles, null));
if (includeSyndicatedArticles) {
buf.append(getMulticategorySyndicationSQL(showUnsyndicatedArticles, syndicationCategories, syndicationPartners));
}
buf.append(" ) ");
buf.append("AND (r.TOTAL_ACTUAL_RATING IS NULL OR r.detail_Type_ID = ");
buf.append(PageElement.DETAIL_TYPE_ARTICLE_ID);
buf.append(") ");
buf.append("AND NVL(r.RATING_INTERVAL_IDENTIFIER,0) = " + Rating.ALL_TIME_COUNT + " ");
buf.append("AND a.ARTL_ID = r.DETAIL_ID (+) ");
if (liveArticlesOnly) {
buf.append(LIVE_ARTICLES_SQL);
}
buf.append(Article.getVideoInclusionSQL(videoInclusion, "a."));
buf.append("ORDER BY ratingValue DESC, article_date DESC ");
buf.append(") a ");
buf.append(")");
buf.append("WHERE rnum < ?");
buf.append(" AND rnum >= ?");
return buf.toString();
}
/**
* Returns an array of <code>Articles</code> found in the database for
* the specified site and pages and also for the specified list of
* syndicated partners and syndicated categories. It also takes in an
* integer defining how many articles to be returned by this method
* and in what position start (for pagination purposes). The articles
* returned will be order by Rating. The articles also have the rating populated.
*
* @param site Site by which the non syndicated articles have to be
* owned by.
*
* @param pages The pages whose category the non syndicated articles
* have to be associated to.
* @param syndicationPartners List of sites by which the syndicated articles
* have to be owned by.
* @param syndicationCategories List of categories to which the syndicated
* articles have to be associated to.
* @param allSyndicatedCategories Whether to all syndicated articles from
* the syndicated partners independently of
* to which category they belong. If this
* flag is set to true the list of categories
* in the syndicationCategories parameter will
* be ignored.
* @param startFromPosition Allows the article index to be offset from the
* start. For example, if this is set to 2, the method
* will still return the specified maximum number of
* articles, but will skip over the article which would
* otherwise have been first.
* @param maxNumArticles The maximum number of articles to return.
* @param liveArticlesOnly If true only live articles are counted
* @param videoInclusion Determines whether articles with videos should be
* returned. This has one of three values:
* 1) WITH_VIDEOS only articles with videos are
* returned
* 2) WITHOUT_VIDEOS only articles without videos are
* returned
* 3) ALL_ARTICLES all articles regardless of whether
* they have videos are returned
*
* @param showUnsyndicatedArticles whether to include unsyndicated articles as well
* @param dbConnection A <code>Connection</code> to use in the method.
* @param logger A <code>Logger</code> to use in the method.
*
* @return An array of articles for the specified site and pages and also
* for the specified list of syndicated partners and syndicated categories.
*
* @exception SQLException if a database access error occurs
*/
private static Article[] getMulticategoryArticlesByRating(Site site,
Page[] pages,
Site[] syndicationPartners,
Category[] syndicationCategories,
boolean allSyndicatedCategories,
int startFromPosition,
int maxNumArticles,
boolean liveArticlesOnly,
int videoInclusion,
boolean showUnsyndicatedArticles,
Connection con,
Logger logger) throws SQLException {
Article[] articlesToReturn = null;
boolean includeSyndicatedArticles =
getIncludeSyndicatedArticles(syndicationPartners, allSyndicatedCategories,
syndicationCategories);
String query = getMulticategoryArticlesByRatingSQL(site, pages,
syndicationPartners, syndicationCategories, allSyndicatedCategories, includeSyndicatedArticles,
startFromPosition, maxNumArticles, liveArticlesOnly, videoInclusion, showUnsyndicatedArticles, logger);
PreparedStatement ps = null;
try {
ps = ConnectionPool.prepareStatement(query, con, logger);
int param = 0;
if (pages != null && pages.length > 0) {
ps.setInt(++param, site.getId());
for (int p=0; p<pages.length; p++) {
if (pages[p].getCategory() != null) {
ps.setInt(++param, pages[p].getCategory().getCategoryId());
}
}
}
if (includeSyndicatedArticles) {
for (int sc=0; sc<syndicationCategories.length; sc++) {
ps.setInt(++param, syndicationCategories[sc].getCategoryId());
}
for (int sp=0; sp<syndicationPartners.length; sp++) {
ps.setInt(++param, syndicationPartners[sp].getId());
}
}
ps.setInt(++param, startFromPosition + maxNumArticles);
ps.setInt(++param, startFromPosition);
articlesToReturn =
Article.getArticles(
ps,
true,
(syndicationCategories == null || syndicationCategories.length == 0)
? site: null,
false,
con,
logger);
} finally {
ps.close();
}
return articlesToReturn;
}
/**
* Returns a <code>String</code> object containing SQL to retrieve
* the specified site and pages and also for the specified list of
* syndicated partners and syndicated categories. It also takes in an
* integer defining how many articles to be returned by this method
* and in what position start (for pagination purposes). The articles
* returned will be order by Most Viewed. The articles also have the total hits populated.
*
* @param site Site by which the non syndicated articles have to be
* owned by.
*
* @param pages The pages whose category the non syndicated articles
* have to be associated to.
* @param syndicationPartners List of sites by which the syndicated articles
* have to be owned by.
* @param syndicationCategories List of categories to which the syndicated
* articles have to be associated to.
* @param allSyndicatedCategories Whether to all syndicated articles from
* the syndicated partners independently of
* to which category they belong. If this
* flag is set to true the list of categories
* in the syndicationCategories parameter will
* be ignored.
* @param startFromPosition Allows the article index to be offset from the
* start. For example, if this is set to 2, the method
* will still return the specified maximum number of
* articles, but will skip over the article which would
* otherwise have been first.
* @param maxNumArticles The maximum number of articles to return.
* @param liveArticlesOnly If true only live articles are counted
* @param videoInclusion Determines whether articles with videos should be
* returned. This has one of three values:
* 1) WITH_VIDEOS only articles with videos are
* returned
* 2) WITHOUT_VIDEOS only articles without videos are
* returned
* 3) ALL_ARTICLES all articles regardless of whether
* they have videos are returned
*
* @param logger A <code>Logger</code> to use in the method.
*
* @return A SQL String to retrieve Multicategor yArticles Ordered By Rating.
*/
private static String getMulticategoryArticlesByMostViewedSQL(Site site,
Page[] pages,
Site[] syndicationPartners,
Category[] syndicationCategories,
boolean allSyndicatedCategories,
boolean includeSyndicatedArticles,
int startFromPosition,
int maxNumArticles,
boolean liveArticlesOnly,
int videoInclusion,
boolean showUnsyndicatedArticles,
Logger logger) {
StringBuffer buf = new StringBuffer();
buf.append("SELECT * FROM ( ");
buf.append("SELECT rownum as rnum, ");
buf.append(SQL_ARTICLE_COLUMNS);
buf.append("FROM ( ");
buf.append("SELECT ");
buf.append(SQL_ARTICLE_COLUMNS);
buf.append(", NVL(ti.total_hits, 0) AS hits ");
buf.append("FROM editorial_articles a, TRACKING_INFO ti ");
buf.append("WHERE ( ");
buf.append(getMulticategoryClubAndCategorySQL(pages, site, includeSyndicatedArticles, "a"));
if (includeSyndicatedArticles) {
buf.append(getMulticategorySyndicationSQL(showUnsyndicatedArticles, syndicationCategories, syndicationPartners));
}
buf.append(" ) ");
buf.append("AND (ti.catg_id IS NULL OR ti.catg_id = 1) ");
buf.append("AND (ti.detail_type_id IS NULL OR ti.detail_type_id = 1) ");
buf.append("AND a.ORGN_CLUB_ID = ti.ORGN_ID(+) ");
buf.append("AND a.ARTL_ID = ti.OBJECT_ID(+) ");
if (liveArticlesOnly) {
buf.append(LIVE_ARTICLES_SQL);
}
buf.append(Article.getVideoInclusionSQL(videoInclusion, "a."));
buf.append("ORDER BY hits DESC, article_date DESC ");
buf.append(") a ");
buf.append(")");
buf.append("WHERE rnum < ? ");
buf.append(" AND rnum >= ?");
return buf.toString();
}
/**
* Returns an array of <code>Articles</code> found in the database for
* the specified site and pages and also for the specified list of
* syndicated partners and syndicated categories. It also takes in an
* integer defining how many articles to be returned by this method
* and in what position start (for pagination purposes). The articles
* returned will be order by Most viewed. The articles also have the total hits populated.
*
* @param site Site by which the non syndicated articles have to be
* owned by.
*
* @param pages The pages whose category the non syndicated articles
* have to be associated to.
* @param syndicationPartners List of sites by which the syndicated articles
* have to be owned by.
* @param syndicationCategories List of categories to which the syndicated
* articles have to be associated to.
* @param allSyndicatedCategories Whether to all syndicated articles from
* the syndicated partners independently of
* to which category they belong. If this
* flag is set to true the list of categories
* in the syndicationCategories parameter will
* be ignored.
* @param startFromPosition Allows the article index to be offset from the
* start. For example, if this is set to 2, the method
* will still return the specified maximum number of
* articles, but will skip over the article which would
* otherwise have been first.
* @param maxNumArticles The maximum number of articles to return.
* @param liveArticlesOnly If true only live articles are counted
* @param videoInclusion Determines whether articles with videos should be
* returned. This has one of three values:
* 1) WITH_VIDEOS only articles with videos are
* returned
* 2) WITHOUT_VIDEOS only articles without videos are
* returned
* 3) ALL_ARTICLES all articles regardless of whether
* they have videos are returned
*
* @param showUnsyndicatedArticles whether to include unsyndicated articles as well
* @param dbConnection A <code>Connection</code> to use in the method.
* @param logger A <code>Logger</code> to use in the method.
*
* @return An array of articles for the specified site and pages and also
* for the specified list of syndicated partners and syndicated categories.
*
* @exception SQLException if a database access error occurs
*/
private static Article[] getMulticategoryArticlesByMostViewed(Site site,
Page[] pages,
Site[] syndicationPartners,
Category[] syndicationCategories,
boolean allSyndicatedCategories,
int startFromPosition,
int maxNumArticles,
boolean liveArticlesOnly,
int videoInclusion,
boolean showUnsyndicatedArticles,
Connection con,
Logger logger) throws SQLException {
Article[] articlesToReturn = null;
boolean includeSyndicatedArticles =
getIncludeSyndicatedArticles(syndicationPartners, allSyndicatedCategories,
syndicationCategories);
String query = getMulticategoryArticlesByMostViewedSQL(site, pages,
syndicationPartners, syndicationCategories, allSyndicatedCategories, includeSyndicatedArticles,
startFromPosition, maxNumArticles, liveArticlesOnly, videoInclusion, showUnsyndicatedArticles, logger);
PreparedStatement ps = null;
try {
ps = ConnectionPool.prepareStatement(query, con, logger);
int param = 0;
if (pages != null && pages.length > 0) {
ps.setInt(++param, site.getId());
for (int p=0; p<pages.length; p++) {
if (pages[p].getCategory() != null) {
ps.setInt(++param, pages[p].getCategory().getCategoryId());
}
}
}
if (includeSyndicatedArticles) {
for (int sc=0; sc<syndicationCategories.length; sc++) {
ps.setInt(++param, syndicationCategories[sc].getCategoryId());
}
for (int sp=0; sp<syndicationPartners.length; sp++) {
ps.setInt(++param, syndicationPartners[sp].getId());
}
}
ps.setInt(++param, startFromPosition + maxNumArticles);
ps.setInt(++param, startFromPosition);
articlesToReturn =
Article.getArticles(
ps,
true,
(syndicationCategories == null || syndicationCategories.length == 0)
? site: null,
false,
con,
logger);
} finally {
ps.close();
}
return articlesToReturn;
}
/**
* Returns an array of <code>Articles</code> found in the database for
* the specified site and pages and also for the specified list of
* syndicated partners and syndicated categories. It also takes in an
* integer defining how many articles to be returned by this method
* and in what position start (for pagination purposes). The articles
* returned will be order by most recent.
*
* @param site Site by which the non syndicated articles have to be
* owned by.
*
* @param pages The pages whose category the non syndicated articles
* have to be associated to.
* @param syndicationPartners List of sites by which the syndicated articles
* have to be owned by.
* @param syndicationCategories List of categories to which the syndicated
* articles have to be associated to.
* @param allSyndicatedCategories Whether to all syndicated articles from
* the syndicated partners independently of
* to which category they belong. If this
* flag is set to true the list of categories
* in the syndicationCategories parameter will
* be ignored.
* @param startFromPosition Allows the article index to be offset from the
* start. For example, if this is set to 2, the method
* will still return the specified maximum number of
* articles, but will skip over the article which would
* otherwise have been first.
* @param maxNumArticles The maximum number of articles to return.
* @param liveArticlesOnly If true only live articles are counted
* @param videoInclusion Determines whether articles with videos should be
* returned. This has one of three values:
* 1) WITH_VIDEOS only articles with videos are
* returned
* 2) WITHOUT_VIDEOS only articles without videos are
* returned
* 3) ALL_ARTICLES all articles regardless of whether
* they have videos are returned
* @param showUnsyndicatedArticles whether to include unsyndicated articles as well
*
* @param dbConnection A <code>Connection</code> to use in the method.
* @param logger A <code>Logger</code> to use in the method.
*
* @return An array of articles for the specified site and pages and also
* for the specified list of syndicated partners and syndicated categories.
*
* @exception SQLException if a database access error occurs
*/
public static Article[] getMulticategoryArticles(int orderBy, Site site,
Page[] pages,
Site[] syndicationPartners,
Category[] syndicationCategories,
boolean allSyndicatedCategories,
int startFromPosition,
int maxNumArticles,
boolean liveArticlesOnly,
int videoInclusion,
boolean showUnsyndicatedArticles,
Connection con,
Logger logger) throws SQLException {
//
// Return value
//
Article[] articlesToReturn = null;
if (orderBy == MulticategoryArticleIndex.ORDER_BY_RATING) {
articlesToReturn = getMulticategoryArticlesByRating(site,
pages,
syndicationPartners,
syndicationCategories,
allSyndicatedCategories,
startFromPosition,
maxNumArticles,
liveArticlesOnly,
videoInclusion,
showUnsyndicatedArticles,
con,
logger);
} else if (orderBy == MulticategoryArticleIndex.ORDER_BY_MOST_VIEWED) {
articlesToReturn = getMulticategoryArticlesByMostViewed(site,
pages,
syndicationPartners,
syndicationCategories,
allSyndicatedCategories,
startFromPosition,
maxNumArticles,
liveArticlesOnly,
videoInclusion,
showUnsyndicatedArticles,
con,
logger);
} else {
String query =
"SELECT * FROM ( " +
"SELECT rownum as rnum, " + SQL_ARTICLE_COLUMNS + " FROM ( " +
"SELECT " + SQL_ARTICLE_COLUMNS +
"FROM editorial_articles a " +
"WHERE ( ";
boolean includeSyndicatedArticles =
getIncludeSyndicatedArticles(syndicationPartners, allSyndicatedCategories, syndicationCategories);
query += getMulticategoryClubAndCategorySQL(pages, site, includeSyndicatedArticles, null);
if (includeSyndicatedArticles) {
query += getMulticategorySyndicationSQL(showUnsyndicatedArticles, syndicationCategories, syndicationPartners);
}
query += " ) " + (liveArticlesOnly ? LIVE_ARTICLES_SQL : "") +
Article.getVideoInclusionSQL(videoInclusion, "a.") +
(orderBy == MulticategoryArticleIndex.ORDER_BY_NAME ?
"ORDER BY headline ASC" : "ORDER BY article_date DESC ") +
") a " +
")" +
" WHERE rnum < ?" +
" AND rnum >= ?";
PreparedStatement ps = null;
try {
ps = ConnectionPool.prepareStatement(query, con, logger);
int param = 0;
if(pages != null && pages.length > 0) {
ps.setInt(++param, site.getId());
for (int p=0; p<pages.length; p++) {
if (pages[p].getCategory() != null) {
ps.setInt(++param, pages[p].getCategory().getCategoryId());
}
}
}
if (includeSyndicatedArticles) {
for (int sc=0; sc<syndicationCategories.length; sc++) {
ps.setInt(++param, syndicationCategories[sc].getCategoryId());
}
for (int sp=0; sp<syndicationPartners.length; sp++) {
ps.setInt(++param, syndicationPartners[sp].getId());
}
}
ps.setInt(++param, startFromPosition + maxNumArticles);
ps.setInt(++param, startFromPosition);
articlesToReturn =
Article.getArticles(
ps,
true,
(syndicationCategories == null ||
syndicationCategories.length == 0)?site:null,
false,
con,
logger);
} finally {
ps.close();
}
}
return articlesToReturn;
}
/**
* Returns a flag indicating if the specified article has a teaser video.
*
* @param articleId The article id.
* @param dbConnection Object containing a database connection.
* @param logger Used for for accessing the logs file.
*
* @return true - If the article has a teaser video.
* false - If the article does not exist or does not
* have a teaser video.
*
* @throws SQLException due to database error.
*/
public static boolean hasTeaserVideo(int articleId,
Connection dbConnection,
Logger logger) throws SQLException {
boolean hasTeaserVideo = false;
PreparedStatement query = ConnectionPool.prepareStatement(
" SELECT teaser_video " +
" FROM article_teaser_videos " +
" WHERE artl_id = ? ",
ResultSet.TYPE_SCROLL_INSENSITIVE,
ResultSet.CONCUR_READ_ONLY,
dbConnection,
logger
);
try {
query.setInt(1, articleId);
ResultSet result =
ConnectionPool.executeQuery(
query,
dbConnection,
logger);
if (result.next()) {
if (result.getString("teaser_video") != null &&
!result.getString("teaser_video").trim().equals("")) {
hasTeaserVideo = true;
}
}
} finally {
if (query != null) {
query.close();
}
}
return hasTeaserVideo;
}
/**
* Returns an array of <code>Articles</code> for a given Category. This
* method is used by the article library and does not instantiate the
* images with the article, nor does it return associated players or matches.
* <p>
*
* Returned articles are ordered by article date descending, and a start
* index and the number of articles to retrieve must be specified.
*
* @param category The Category for which a list of articles is requested.
* If null, all categories will be included.
* @param startIndex Determines where in the result set the returned
* will start form. This is used to page through articles
* in the article library.
* The order by which results are returned. If this is set
* to <code>true</true> results will be order by headline,
* if it is <code>false</false> results will be order by
* last updated date.
* @param numberOfArticles The number of Articles that are returned.
* @param site The site for which articles are being requested.
* Only articles written by this site will be returned.
* @param endDate If present, only articles up to and including the
* specified date will be retrieved.
* @param startDate If present only articles after and including the
* specified date will be retrieved.
* @param restrictToNewsletterArticles If true, only articles marked for
* newsletters will be returned.
* @param liveArticlesOnly restrict results to those articles which are live.
* @param dbConnection A <code>Connection</code> to use in the method.
* @param logger A <code>Logger</code> to use in the method.
*
* @return The <code>Article</code> objects requested, or null if not found.
*
* @exception SQLException if a database access error occurs
*/
public static Article[] getArticles(Category category,
int startIndex,
int numberOfArticles,
Site site,
Date startDate,
Date endDate,
boolean restrictToNewsletterArticles,
boolean liveArticlesOnly,
Connection dbConnection,
Logger logger)
throws SQLException {
String liveArticleCondition = new StringBuilder(100)
.append(" AND SYSDATE >= article_date " )
.append(" AND SYSDATE >= NVL(site_posted_date,TO_DATE('01-Jan-3001', 'DD-Mon-YYYY')) ")
.append(" AND SYSDATE < NVL(site_removed_date,TO_DATE('01-Jan-3001', 'DD-Mon-YYYY'))").toString();
StringBuilder queryString = new StringBuilder(1000)
.append("SELECT ")
.append(Article.SQL_ARTICLE_COLUMNS)
.append(" FROM ")
.append("(select ea.*, row_number() over(order by article_date DESC) rn ")
.append("from editorial_articles ea ")
.append("where orgn_club_id = ?")
.append(null != category ? " AND catg_id = ? " : "")
.append(null != startDate ? " AND article_date >= ? " : "")
.append(null != endDate ? " AND article_date <= ? " : "")
.append(restrictToNewsletterArticles ? " AND newsletter_flg = 'Y' " : "")
.append(liveArticlesOnly ? liveArticleCondition : "")
.append(" ) a WHERE rn BETWEEN ? AND ?");
PreparedStatement query = ConnectionPool.prepareStatement(queryString.toString(), dbConnection, logger);
Article[] articles = null;
try {
int param = 1;
query.setInt(param++, site.getId());
if (null != category) {
query.setInt(param++, category.getCategoryId());
}
if (null != startDate) {
query.setDate(param++, new java.sql.Date(startDate.getTime()));
}
if (null != endDate) {
query.setDate(param++, new java.sql.Date(endDate.getTime()));
}
query.setInt(param++, startIndex);
query.setInt(param++, startIndex + numberOfArticles - 1);
//
// Construct Article objects based on the above SQL <i>without</i> images;
//
articles = Article.getArticles(query,
false,
site,
false,
dbConnection,
logger);
} finally {
query.close();
}
return articles;
}
/**
* The method returns the SQL clause required for
* filtering the detail ids.This method also checks
* if the detailtype is a linked detailtype or not
* and accordingly creates query.This method also
* includes the syndication categories if there
* exist any.
*
* @param site A <code>Site</code> instance
* @param category A <code>Category</code> instance, if not null,
* represents the articles of the supplied category.
* @param detailType A DetailType for articles.
* This can be article detail type or any other linked
* detail type like Match, Player etc.
*
* @return The filter SQL clause for the detail ids
*/
private static String getFilterDetailIdClause(
Site site,
Category[] categories,
DetailType detailType,
Site[]syndicationPartners,
Category[]syndicationCategories,
boolean allSyndicatedCategories ) {
String filterDetailIdClause = null;
boolean includeSyndicatedArticles =
getIncludeSyndicatedArticles(syndicationPartners, allSyndicatedCategories,
syndicationCategories);
String queryString = "";
//
// Query for including all the categories for the
// pages selected
//
if(null != categories && categories.length > 0){
queryString = "(( orgn_club_id = "+site.getId()+" OR general_flg = 'Y' ) " +
"AND catg_id IN ( " +getCommaSeperatedCategories(categories)+ " ) " +
LIVE_ARTICLES_SQL.replaceAll("a\\.", "") +
" ) " +
(includeSyndicatedArticles ? " OR " :"") ;
}
//
//create sql query for the syndication categories
//and also for the syndication partners if they exist.
//
if (includeSyndicatedArticles) {
queryString += " (syndicated_flg = 'Y' ";
if (syndicationCategories != null && syndicationCategories.length > 0) {
queryString += " AND catg_id in (";
for (int i=0; i<syndicationCategories.length-1; i++) {
queryString += syndicationCategories[i].getCategoryId() +
",";
}
queryString +=
syndicationCategories[syndicationCategories.length-1].getCategoryId()
+ ") ";
}
//
//include all the syndication parteners in the query
//for retriving all the articles of the pages.
//
queryString += " AND orgn_club_id in (";
for (int i=0; i<syndicationPartners.length-1; i++) {
queryString += syndicationPartners[i].getId() + ",";
}
queryString +=
syndicationPartners[syndicationPartners.length-1].getId() + ")) ";
}
//
//Check if the categories are for linked detatType.
//
boolean isCategoryForLinkedDetailTypes = (null != categories
&& detailType.getId() != PageElement.DETAIL_TYPE_ARTICLE_ID);
//
// If isCategoryForLinkedDetailTypes is false then there
// exist a detailtype whose detailtypeId is Article detail Type.
//
if(!isCategoryForLinkedDetailTypes){
filterDetailIdClause =
"SELECT " +
"articles.artl_id " +
"FROM " +
"editorial_articles articles " +
"WHERE " +
"( "
+ queryString +
" ) " ;
}
//
// If isCategoryForLinkedDetailTypes is true then there
// exist a detailtype whose detailtypeId is of linked detail Type.
//
else{
filterDetailIdClause =
"SELECT links.link_id " +
"FROM " +
"editorial_articles articles , " +
"article_links links " +
"WHERE articles.artl_id = links.artl_id " +
"AND links.detail_type_id = " +detailType.getId()+" "+
" AND ( " +
"( " +
"articles.orgn_club_id = " + site.getId() + " " +
"OR " +
"articles.general_flg = 'Y' " +
") " +
"AND " +
"articles.catg_id IN ( " +
getCommaSeperatedCategories(categories) +
" ) " +
") ";
}
return filterDetailIdClause;
}
/**
* Returns an array of <code>Articles</code> for a given Category
* when the ordering is by date or headline(name). This
* method is used by the article library and does not instantiate the
* images with the article, nor does it return associated players or matches.
* Returned articles are ordered by article date descending, and a start
* index and the number of articles to retrieve must be specified.
*
* @param orderBy The order by criteria - can be either name or date.
* @param category The Category for which a list of articles is requested.
* If null, all categories will be included.
* @param startIndex Determines where in the result set the returned
* will start form. This is used to page through articles
* in the article library.
* The order by which results are returned. If this is set
* to <code>true</true> results will be order by headline,
* if it is <code>false</false> results will be order by
* last updated date.
* @param numberOfArticles The number of Articles that are returned.
* @param site The site for which articles are being requested.
* Only articles written by this site will be returned.
* @param excludeHomePageArticles If true, excludes Home Page articles.
* @param liveArticlesOnly restrict results to those articles which are live.
* @param excludeArticlesFor An Enumerator that specifies whether the
* articles needs to be excluded for comments or
* ratings.If null, articles will not be excluded.
* @param isDescendingSort A flag that tells whether articles needs to be
* sorted in descending order or ascending order.
* @param homePageArticlesOnly if true only home page articles are counted
* @param detailType The <code>DetailType</code> associated with the article
* @param videoInclusion Determines whether articles with videos should be
* counted. This has one of three values:
* 1) {@link Article#WITH_VIDEOS} only articles with
* videos are counted
* 2) {@link Article#WITHOUT_VIDEOS} only articles
* without videos are counted
* 3) {@link Article#ALL_ARTICLES} all articles
* regardless of whether they have videos are
* counted
* @param dbConnection A <code>Connection</code> to use in the method.
* @param logger A <code>Logger</code> to use in the method.
*
* @return The <code>Article</code> objects requested, or null if not found.
*
* @exception SQLException if a database access error occurs
*/
public static Article[] getArticlesforCustomArticleIndex(
int orderBy,
Category category,
int startIndex,
int numberOfArticles,
Site site,
boolean excludeHomePageArticles,
boolean liveArticlesOnly,
ExcludeArticlesFor excludeArticlesFor,
boolean isDescendingSort,
boolean homePageArticlesOnly,
DetailType detailType,
int videoInclusion,
Connection dbConnection,
Logger logger
) throws SQLException {
Article[] articles = null;
if (orderBy == CustomArticleIndex.ORDER_BY_RATING) {
articles = getArticlesforCustomArticleIndexByRating(
new Category[]{category},
startIndex,
numberOfArticles,
site,
excludeHomePageArticles,
liveArticlesOnly,
excludeArticlesFor,
isDescendingSort,
homePageArticlesOnly,
detailType,
null,
null,
false,
videoInclusion,
dbConnection,
logger
);
} else if (orderBy == CustomArticleIndex.ORDER_BY_MOST_VIEWED) {
articles = getArticlesforCustomArticleIndexByMostViewed(
new Category[]{category},
startIndex,
numberOfArticles,
site,
excludeHomePageArticles,
liveArticlesOnly,
excludeArticlesFor,
isDescendingSort,
homePageArticlesOnly,
detailType,
null,
null,
false,
videoInclusion,
dbConnection,
logger
);
} else {
articles = getArticlesforCustomArticleIndex(
orderBy,
new Category[]{category},
startIndex,
numberOfArticles,
site,
excludeHomePageArticles,
liveArticlesOnly,
excludeArticlesFor,
isDescendingSort,
(null == category),
detailType,
null, // syndicationPartners,
null, // syndicationCategories
false, // allSyndicatedCategories
videoInclusion, // videoInclusion
dbConnection,
logger
);
}
return articles;
}
/**
* Returns an array of <code>Articles</code> for a given Category
* when the ordering is by Rating.
* Returned articles are ordered by Rating then by article date descending,
* and a start index and the number of articles to retrieve must be specified.
*
* @param orderBy The order by criteria - can be either name or date.
* @param category The Category for which a list of articles is requested.
* If null, all categories will be included.
* @param startIndex Determines where in the result set the returned
* will start form. This is used to page through articles
* in the article library.
* The order by which results are returned. If this is set
* to <code>true</true> results will be order by headline,
* if it is <code>false</false> results will be order by
* last updated date.
* @param numberOfArticles The number of Articles that are returned.
* @param site The site for which articles are being requested.
* Only articles written by this site will be returned.
* @param excludeHomePageArticles If true, excludes Home Page articles.
* @param liveArticlesOnly restrict results to those articles which are live.
* @param excludeArticlesFor An Enumerator that specifies whether the
* articles needs to be excluded for comments or
* ratings.If null, articles will not be excluded.
* @param isDescendingSort A flag that tells whether articles needs to be
* sorted in descending order or ascending order.
* @param homePageArticlesOnly if true only home page articles are counted
* @param detailType The <code>DetailType</code> associated with the article
* @param dbConnection A <code>Connection</code> to use in the method.
* @param logger A <code>Logger</code> to use in the method.
*
* @return The <code>Article</code> objects requested, or null if not found.
*
* @exception SQLException if a database access error occurs
*/
public static Article[] getArticlesforCustomArticleIndexByRating(
Category[] categories,
int startIndex,
int numberOfArticles,
Site site,
boolean excludeHomePageArticles,
boolean liveArticlesOnly,
ExcludeArticlesFor excludeArticlesFor,
boolean isDescendingSort,
boolean homePageArticlesOnly,
DetailType detailType,
Site[] syndicationPartners,
Category[] syndicationCategories,
boolean allSyndicatedCategories,
int videoInclusion,
Connection dbConnection,
Logger logger
) throws SQLException {
Article[] articles = null;
int[] articleIds = Article.getArticleIdsByRating(categories,
startIndex,
numberOfArticles,
site,
excludeHomePageArticles,
liveArticlesOnly,
isDescendingSort,
homePageArticlesOnly,
syndicationPartners,
syndicationCategories,
allSyndicatedCategories,
videoInclusion,
dbConnection,
logger);
articles = Article.getArticles(articleIds, liveArticlesOnly, true, site, dbConnection, logger);
return articles;
}
/**
* Returns an array of <code>Articles</code> for a given Category
* when the ordering is by Most Viewed.
* Returned articles are ordered by Most Viewed then by article date descending,
* and a start index and the number of articles to retrieve must be specified.
*
* @param orderBy The order by criteria - can be either name or date.
* @param category The Category for which a list of articles is requested.
* If null, all categories will be included.
* @param startIndex Determines where in the result set the returned
* will start form. This is used to page through articles
* in the article library.
* The order by which results are returned. If this is set
* to <code>true</true> results will be order by headline,
* if it is <code>false</false> results will be order by
* last updated date.
* @param numberOfArticles The number of Articles that are returned.
* @param site The site for which articles are being requested.
* Only articles written by this site will be returned.
* @param excludeHomePageArticles If true, excludes Home Page articles.
* @param liveArticlesOnly restrict results to those articles which are live.
* @param excludeArticlesFor An Enumerator that specifies whether the
* articles needs to be excluded for comments or
* ratings.If null, articles will not be excluded.
* @param isDescendingSort A flag that tells whether articles needs to be
* sorted in descending order or ascending order.
* @param homePageArticlesOnly if true only home page articles are counted
* @param detailType The <code>DetailType</code> associated with the article
* @param dbConnection A <code>Connection</code> to use in the method.
* @param logger A <code>Logger</code> to use in the method.
*
* @return The <code>Article</code> objects requested, or null if not found.
*
* @exception SQLException if a database access error occurs
*/
public static Article[] getArticlesforCustomArticleIndexByMostViewed (
Category[] categories,
int startIndex,
int numberOfArticles,
Site site,
boolean excludeHomePageArticles,
boolean liveArticlesOnly,
ExcludeArticlesFor excludeArticlesFor,
boolean isDescendingSort,
boolean homePageArticlesOnly,
DetailType detailType,
Site[] syndicationPartners,
Category[] syndicationCategories,
boolean allSyndicatedCategories,
int videoInclusion,
Connection dbConnection,
Logger logger
) throws SQLException {
Article[] articles = null;
int[] articleIds = Article.getArticleIdsByMostViewed(categories,
startIndex,
numberOfArticles,
site,
excludeHomePageArticles,
liveArticlesOnly,
isDescendingSort,
homePageArticlesOnly,
syndicationPartners,
syndicationCategories,
allSyndicatedCategories,
videoInclusion,
dbConnection,
logger);
articles = Article.getArticles(articleIds, liveArticlesOnly, true, site, dbConnection, logger);
return articles;
}
/**
* Returns a SQL <code>String</code> object for selecting articles then ordering
* them by the articles rating
*
* @param category The Category for which a list of articles is requested.
* If null, all categories will be included.
* @param excludeHomePageArticles If true, excludes Home Page articles.
* @param liveArticlesOnly restrict results to those articles which are live.
* @param isDescendingSort A flag that tells whether articles needs to be
* sorted in descending order or ascending order.
* @param homePageArticlesOnly if true only home page articles are counted
* @param syndicationPartners List of sites by which the syndicated articles
* have to be owned by.
* @param syndicationCategories List of categories to which the syndicated
* articles have to be associated to.
* @param allSyndicatedCategories Whether to all syndicated articles from
* the syndicated partners independently of
* to which category they belong. If this
* flag is set to true the list of categories
* in the syndicationCategories parameter will
* be ignored.
* @param videoInclusion Determines whether articles with videos should be
* counted. This has one of three values:
* 1) WITH_VIDEOS only articles with videos are
* counted
* 2) WITHOUT_VIDEOS only articles without videos are
* counted
* 3) ALL_ARTICLES all articles regardless of whether
* they have videos are counted
*
* @return The <code>String</code> object containing the SQL to Select Articles
* by rating.
*
*/
private static String constructSelectByRatingSQL(Site site,
Category[] categories,
boolean excludeHomePageArticles,
boolean liveArticlesOnly,
boolean isDescendingSort,
boolean homePageArticlesOnly,
Site[] syndicationPartners,
Category[] syndicationCategories,
boolean allSyndicatedCategories,
boolean includeSyndicatedArticles,
int videoInclusion) {
//
// Query for including all the categories for the
// pages selected
//
String categoriesAndSyndicationSQL =
getCategoriesAndSyndicationSQL(categories, includeSyndicatedArticles,
syndicationCategories, syndicationPartners, null);
StringBuffer buf = new StringBuffer();
buf.append(" SELECT artl_id ");
buf.append(" FROM ");
buf.append(" ( ");
buf.append(" SELECT artl_id, rownum as rnum ");
buf.append(" FROM ");
buf.append(" (");
buf.append(" SELECT ea.ARTL_ID, ");
buf.append(" nvl((r.TOTAL_ACTUAL_RATING/r.NUMBER_OF_USERS_RATED),?");
buf.append(") as ratingValue");
buf.append(" FROM EDITORIAL_ARTICLES ea, Rating r");
buf.append(" WHERE " );
buf.append(categoriesAndSyndicationSQL);
buf.append(getExcludeHomepageArticlesSQL(categories, excludeHomePageArticles, "ea"));
buf.append(" AND (r.TOTAL_ACTUAL_RATING IS NULL OR r.detail_Type_ID = ");
buf.append(PageElement.DETAIL_TYPE_ARTICLE_ID);
buf.append(")");
buf.append(" AND NVL(r.RATING_INTERVAL_IDENTIFIER,0) = " + Rating.ALL_TIME_COUNT + " ");
buf.append(" AND ea.ARTL_ID = r.DETAIL_ID (+)");
buf.append(getLiveArticlesSQL(liveArticlesOnly));
buf.append(getVideoInclusionSQL(videoInclusion));
buf.append(getHomepageOnlySQL(homePageArticlesOnly));
buf.append(" ORDER BY ratingValue DESC, article_date DESC");
buf.append(" )");
buf.append(" )");
buf.append(" WHERE rnum BETWEEN ? AND ? ");
return buf.toString();
}
/**
* Returns a <code>int[]</code> object containing the articleIds order by the
* articles rating that matches the given SQL Conditions
*
* @param categories The Category for which a list of articles is requested.
* If null, all categories will be included.
* @param startIndex Determines where in the result set the returned
* will start form. This is used to page through articles
* in the article library.
* The order by which results are returned. If this is set
* to <code>true</true> results will be order by headline,
* if it is <code>false</false> results will be order by
* last updated date.
* @param numberOfArticles The number of Articles that are returned.
* @param site The site for which articles are being requested.
* Only articles written by this site will be returned.
* @param excludeHomePageArticles If true, excludes Home Page articles.
* @param liveArticlesOnly restrict results to those articles which are live.
* @param isDescendingSort A flag that tells whether articles needs to be
* sorted in descending order or ascending order.
* @param homePageArticlesOnly if true only home page articles are counted
* @param syndicationPartners List of sites by which the syndicated articles
* have to be owned by.
* @param syndicationCategories List of categories to which the syndicated
* articles have to be associated to.
* @param allSyndicatedCategories Whether to all syndicated articles from
* the syndicated partners independently of
* to which category they belong. If this
* flag is set to true the list of categories
* in the syndicationCategories parameter will
* be ignored.
* @param videoInclusion Determines whether articles with videos should be
* counted. This has one of three values:
* 1) WITH_VIDEOS only articles with videos are
* counted
* 2) WITHOUT_VIDEOS only articles without videos are
* counted
* 3) ALL_ARTICLES all articles regardless of whether
* they have videos are counted
* @param dbConnection A <code>Connection</code> to use in the method.
* @param logger A <code>Logger</code> to use in the method.
*
* @return The <code>int[]</code> containing the article Ids ordered by rating.
*
* @exception SQLException if a database access error occurs
*
*/
private static int[] getArticleIdsByRating(Category[] categories,
int startIndex,
int numberOfArticles,
Site site,
boolean excludeHomePageArticles,
boolean liveArticlesOnly,
boolean isDescendingSort,
boolean homePageArticlesOnly,
Site[] syndicationPartners,
Category[] syndicationCategories,
boolean allSyndicatedCategories,
int videoInclusion,
Connection dbConnection,
Logger logger)
throws SQLException {
boolean includeSyndicatedArticles =
getIncludeSyndicatedArticles(syndicationPartners, allSyndicatedCategories,
syndicationCategories);
String selectArticlesByRatingSQL =
constructSelectByRatingSQL(site,
categories,
excludeHomePageArticles,
liveArticlesOnly,
isDescendingSort,
homePageArticlesOnly,
syndicationPartners,
syndicationCategories,
allSyndicatedCategories,
includeSyndicatedArticles,
videoInclusion);
int[] articleIds = null;
PreparedStatement query = ConnectionPool.prepareStatement(selectArticlesByRatingSQL,
dbConnection,
logger
);
try {
int param = 0;
query.setInt(++param, site.getRatingsConfig().getDefaultRatingValue());
query.setInt(++param, site.getId());
if (categories != null && categories.length > 0) {
for (Category cat : categories) {
query.setInt(++param, cat.getCategoryId());
}
}
if (includeSyndicatedArticles) {
if (syndicationCategories != null && syndicationCategories.length > 0) {
for (Category cat : syndicationCategories) {
query.setInt(++param, cat.getCategoryId());
}
}
}
if (syndicationPartners != null && syndicationPartners.length > 0) {
for (Site partner : syndicationPartners) {
query.setInt(++param, partner.getId());
}
}
query.setInt(++param, startIndex);
query.setInt(++param, startIndex + numberOfArticles - 1);
ResultSet rs = ConnectionPool.executeQuery(query, dbConnection, logger);
//
// Loop through results and build articles for each row returned.
//
if (rs != null) {
//
// Find out the array size
//
ArrayList<Integer> list = new ArrayList<Integer>();
while (rs.next()) {
list.add(new Integer(rs.getInt(1)));
}
Iterator<Integer> iter = list.iterator();
articleIds = new int[list.size()];
int pos = 0;
while (iter.hasNext()) {
articleIds[pos++] = iter.next().intValue();
}
}
} finally {
query.close();
}
return articleIds;
}
/**
* Returns a SQL <code>String</code> object for selecting articles then ordering
* them by the articles that are most viewed
*
* @param category The Category for which a list of articles is requested.
* If null, all categories will be included.
* @param excludeHomePageArticles If true, excludes Home Page articles.
* @param liveArticlesOnly restrict results to those articles which are live.
* @param isDescendingSort A flag that tells whether articles needs to be
* sorted in descending order or ascending order.
* @param homePageArticlesOnly if true only home page articles are counted
* @param syndicationPartners List of sites by which the syndicated articles
* have to be owned by.
* @param syndicationCategories List of categories to which the syndicated
* articles have to be associated to.
* @param allSyndicatedCategories Whether to all syndicated articles from
* the syndicated partners independently of
* to which category they belong. If this
* flag is set to true the list of categories
* in the syndicationCategories parameter will
* be ignored.
* @param videoInclusion Determines whether articles with videos should be
* counted. This has one of three values:
* 1) WITH_VIDEOS only articles with videos are
* counted
* 2) WITHOUT_VIDEOS only articles without videos are
* counted
* 3) ALL_ARTICLES all articles regardless of whether
* they have videos are counted
*
* @return The <code>String</code> object containing the SQL to Select Articles
* by rating.
*
*/
private static String constructSelectByMostViewedSQL(Category[] categories,
boolean excludeHomePageArticles,
boolean liveArticlesOnly,
boolean isDescendingSort,
boolean homePageArticlesOnly,
Site[] syndicationPartners,
Category[] syndicationCategories,
boolean allSyndicatedCategories,
boolean includeSyndicatedArticles,
int videoInclusion) {
//
// Query for including all the categories for the
// pages selected
//
String categoriesAndSyndicationSQL =
getCategoriesAndSyndicationSQL(categories, includeSyndicatedArticles,
syndicationCategories, syndicationPartners, "ea");
StringBuffer buf = new StringBuffer();
buf.append(" SELECT artl_id ");
buf.append(" FROM ");
buf.append(" ( ");
buf.append(" SELECT artl_id, rownum as rnum ");
buf.append(" FROM ");
buf.append(" (");
buf.append(" SELECT ea.ARTL_ID, NVL(ti.total_hits, 0) AS hits ");
buf.append(" FROM EDITORIAL_ARTICLES ea, TRACKING_INFO ti ");
buf.append(" WHERE " );
buf.append(categoriesAndSyndicationSQL);
buf.append(getExcludeHomepageArticlesSQL(categories, excludeHomePageArticles, "ea"));
buf.append(" AND (ti.detail_type_id IS NULL OR ti.detail_type_id = 1) ");
buf.append(" AND (ti.catg_id IS NULL OR ti.catg_id = 1) ");
buf.append(" AND ea.ORGN_CLUB_ID = ti.ORGN_ID(+) ");
buf.append(" AND ea.ARTL_ID = ti.OBJECT_ID(+) ");
buf.append(getLiveArticlesSQL(liveArticlesOnly));
buf.append(getVideoInclusionSQL(videoInclusion));
buf.append(getHomepageOnlySQL(homePageArticlesOnly));
buf.append(" ORDER BY hits DESC, article_date DESC");
buf.append(" )");
buf.append(" )");
buf.append(" WHERE rnum BETWEEN ? AND ? ");
return buf.toString();
}
/**
* Returns a <code>int[]</code> object containing the articleIds order by the
* articles most viewed that matches the given SQL Conditions
*
* @param categories The Category for which a list of articles is requested.
* If null, all categories will be included.
* @param startIndex Determines where in the result set the returned
* will start form. This is used to page through articles
* in the article library.
* The order by which results are returned. If this is set
* to <code>true</true> results will be order by headline,
* if it is <code>false</false> results will be order by
* last updated date.
* @param numberOfArticles The number of Articles that are returned.
* @param site The site for which articles are being requested.
* Only articles written by this site will be returned.
* @param excludeHomePageArticles If true, excludes Home Page articles.
* @param liveArticlesOnly restrict results to those articles which are live.
* @param isDescendingSort A flag that tells whether articles needs to be
* sorted in descending order or ascending order.
* @param homePageArticlesOnly if true only home page articles are counted
* @param syndicationPartners List of sites by which the syndicated articles
* have to be owned by.
* @param syndicationCategories List of categories to which the syndicated
* articles have to be associated to.
* @param allSyndicatedCategories Whether to all syndicated articles from
* the syndicated partners independently of
* to which category they belong. If this
* flag is set to true the list of categories
* in the syndicationCategories parameter will
* be ignored.
* @param videoInclusion Determines whether articles with videos should be
* counted. This has one of three values:
* 1) WITH_VIDEOS only articles with videos are
* counted
* 2) WITHOUT_VIDEOS only articles without videos are
* counted
* 3) ALL_ARTICLES all articles regardless of whether
* they have videos are counted
* @param dbConnection A <code>Connection</code> to use in the method.
* @param logger A <code>Logger</code> to use in the method.
*
* @return The <code>int[]</code> containing the article Ids ordered by rating.
*
* @exception SQLException if a database access error occurs
*
*/
private static int[] getArticleIdsByMostViewed(Category[] categories,
int startIndex,
int numberOfArticles,
Site site,
boolean excludeHomePageArticles,
boolean liveArticlesOnly,
boolean isDescendingSort,
boolean homePageArticlesOnly,
Site[] syndicationPartners,
Category[] syndicationCategories,
boolean allSyndicatedCategories,
int videoInclusion,
Connection dbConnection,
Logger logger)
throws SQLException {
boolean includeSyndicatedArticles =
getIncludeSyndicatedArticles(syndicationPartners, allSyndicatedCategories,
syndicationCategories);
String selectArticlesByMostViewedSQL =
constructSelectByMostViewedSQL(categories,
excludeHomePageArticles,
liveArticlesOnly,
isDescendingSort,
homePageArticlesOnly,
syndicationPartners,
syndicationCategories,
allSyndicatedCategories,
includeSyndicatedArticles,
videoInclusion);
int[] articleIds = null;
PreparedStatement query = ConnectionPool.prepareStatement(
selectArticlesByMostViewedSQL,
dbConnection,
logger
);
try {
int param = 0;
query.setInt(++param, site.getId());
if (categories != null && categories.length > 0) {
for (Category cat : categories) {
query.setInt(++param, cat.getCategoryId());
}
}
if (includeSyndicatedArticles) {
if (syndicationCategories != null && syndicationCategories.length > 0) {
for (Category cat : syndicationCategories) {
query.setInt(++param, cat.getCategoryId());
}
}
}
if (syndicationPartners != null && syndicationPartners.length > 0) {
for (Site partner : syndicationPartners) {
query.setInt(++param, partner.getId());
}
}
query.setInt(++param, startIndex);
query.setInt(++param, startIndex + numberOfArticles - 1);
ResultSet rs = ConnectionPool.executeQuery(query, dbConnection, logger);
//
// Loop through results and build articles for each row returned.
//
if (rs != null) {
//
// Find out the array size
//
ArrayList<Integer> list = new ArrayList<Integer>();
while (rs.next()) {
list.add(new Integer(rs.getInt(1)));
}
Iterator<Integer> iter = list.iterator();
articleIds = new int[list.size()];
int pos = 0;
while (iter.hasNext()) {
articleIds[pos++] = iter.next().intValue();
}
}
} finally {
query.close();
}
return articleIds;
}
/**
* Returns a <code>String</code> object containing SQL relating to categories
* and syndication
*
* @param categories The Category for which a list of articles is requested.
* If null, all categories will be included.
* @param syndicationCategories List of categories to which the syndicated
* articles have to be associated to.
* @param syndicationPartners List of sites by which the syndicated articles
* have to be owned by.
* @param includeSyndicatedArticles Boolean condition to determine whether to
* include any Syndication Condition in the SQL
*
* @return The <code>String</code> containing SQL relating to categories
* and syndication
*
*/
private static String getCategoriesAndSyndicationSQL(Category[] categories,
boolean includeSyndicatedArticles, Category[] syndicationCategories,
Site[] syndicationPartners, String tablePrefix) {
if(tablePrefix != null && tablePrefix.length() > 0) {
tablePrefix+=".";
} else {
tablePrefix = "";
}
String str = "";
if (null != categories && categories.length > 0){
str = " (( " + tablePrefix + "orgn_club_id = ? OR " + tablePrefix + "general_flg = 'Y' ) " +
" AND " + tablePrefix + "catg_id IN ( " +getCommaSeperatedCategories(categories)+ " ) )" +
(includeSyndicatedArticles ? " OR " :"") ;
}
//
//create sql query for the syndication categories
//and also for the syndication partners if they exist.
//
if (includeSyndicatedArticles) {
str += " (" + tablePrefix + "syndicated_flg = 'Y' ";
if (syndicationCategories != null && syndicationCategories.length > 0) {
str += " AND " + tablePrefix + "catg_id in (";
for (int i=0; i<syndicationCategories.length-1; i++) {
str += "?,";
}
str += "?) ";
}
//
//include all the syndication parteners in the query
//for retriving all the articles of the pages.
//
if (syndicationPartners != null && syndicationPartners.length > 0) {
str += " AND " + tablePrefix + "orgn_club_id in (";
for (int i=0; i<syndicationPartners.length-1; i++) {
str += "?,";
}
str += "?)";
}
str += ") ";
logger.debug("Including Syndicated Articles "+ str);
}
return str;
}
/**
* Returns a <code>boolean</code> object that determines whether we should
* include Syndicated Articles
*
* @param syndicationPartners List of sites by which the syndicated articles
* have to be owned by.
* @param syndicationCategories List of categories to which the syndicated
* articles have to be associated to.
* @param allSyndicatedCategories Whether to all syndicated articles from
* the syndicated partners independently of
* to which category they belong. If this
* flag is set to true the list of categories
* in the syndicationCategories parameter will
* be ignored.
*
* @return The <code>boolean</code> object that determines whether we should
* include Syndicated Articles
*/
private static boolean getIncludeSyndicatedArticles(Site[] syndicationPartners,
boolean allSyndicatedCategories, Category[] syndicationCategories)
{
boolean includeSyndicatedArticles =
(syndicationPartners != null && syndicationPartners.length > 0 &&
( allSyndicatedCategories == true ||
(syndicationCategories != null && syndicationCategories.length > 0)));
return includeSyndicatedArticles;
}
/**
* Returns a <code>String</code> object containing SQL relating to Excluding
* Homepage Articles
*
* @param categories The Category for which a list of articles is requested.
* If null, all categories will be included.
* @param excludeHomePageArticles If true, excludes Home Page articles.
* @param tablePrefix If not null appends the prefix to the table name in the
* SQL code
*
* @return The <code>String</code> object containing SQL relating to Excluding
* Homepage Articles
*
*/
private static String getExcludeHomepageArticlesSQL(Category[] categories,
boolean excludeHomePageArticles, String tablePrefix) {
StringBuffer buf = new StringBuffer(" ");
boolean includeTablePrefix = (tablePrefix != null && tablePrefix.length() > 0);
if (null != categories && excludeHomePageArticles) {
buf.append("AND ");
if (includeTablePrefix) {
buf.append(tablePrefix);
buf.append(".");
}
buf.append("hmpg_flg = 'N' ");
}
return buf.toString();
}
/**
* Returns a <code>String</code> object containing SQL relating to retrieving
* Live Articles
*
* @param liveArticlesOnly true, return SQL that returns livae articles, false
* return SQL that gets all.
*
* @return The <code>String</code> object containing SQL relating to retrieving
* Live Articles
*
*/
private static String getLiveArticlesSQL(boolean liveArticlesOnly)
{
StringBuffer buf = new StringBuffer(" ");
if (liveArticlesOnly) {
buf.append("AND SYSDATE >= article_date ");
buf.append("AND SYSDATE >= NVL(site_posted_date, ");
buf.append("TO_DATE('01-Jan-3001', 'DD-Mon-YYYY')) ");
buf.append("AND SYSDATE < NVL(site_removed_date, ");
buf.append("TO_DATE('01-Jan-3001', 'DD-Mon-YYYY')) ");
}
return buf.toString();
}
/**
* Returns a <code>String</code> object containing SQL relating to Video Inclusion
*
* @param videoInclusion Determines whether articles with videos should be
* counted. This has one of three values:
* 1) {@link Article#WITH_VIDEOS} only articles with
* videos are counted
* 2) {@link Article#WITHOUT_VIDEOS} only articles
* without videos are counted
* 3) {@link Article#ALL_ARTICLES} all articles
* regardless of whether they have videos are
* counted
*
* @return The <code>String</code> object containing SQL relating to Video Inclusion
*/
private static String getVideoInclusionSQL(int videoInclusion) {
return getVideoInclusionSQL(videoInclusion, "");
}
/**
* Get an SQL fragment relating to video inclusion from the SVA article links
* as well as the original article video linking.
*
* @param videoInclusion Determines whether articles with videos should be
* counted. This has one of three values:
* 1) {@link Article#WITH_VIDEOS} only articles with
* videos are counted
* 2) {@link Article#WITHOUT_VIDEOS} only articles
* without videos are counted
* 3) {@link Article#ALL_ARTICLES} all articles
* regardless of whether they have videos are
* counted
* @param tablePrefix The SQL table prefix to use (ie "ea." "a.", etc)
*
* @return The SQL fragment to use to determine if articles are linked to a
* video.
*
* @see Article#WITH_VIDEOS
* @see Article#WITHOUT_VIDEOS
*/
private static String getVideoInclusionSQL(int videoInclusion, String tablePrefix) {
String sqlFragment;
switch(videoInclusion) {
case Article.WITH_VIDEOS:
sqlFragment = MessageFormat.format(FMT_SQL_VIDEOS_ATTACHED,
(tablePrefix != null) ? tablePrefix : "");
break;
case Article.WITHOUT_VIDEOS:
sqlFragment = MessageFormat.format(FMT_SQL_VIDEOS_NOT_ATTACHED,
(tablePrefix != null) ? tablePrefix : "");
break;
default:
sqlFragment = "";
}
return sqlFragment;
}
/**
* Returns a <code>String</code> object containing SQL relating to Homepage Only
* Articles
*
* @param homePageArticlesOnly true - only include homepage articles, false return
* all
*
* @return The <code>String</code> object containing SQL relating to Homepage Only
* Articles
*
*/
private static String getHomepageOnlySQL(boolean homePageArticlesOnly)
{
String str = " ";
if (homePageArticlesOnly) {
str = "AND hmpg_flg = 'Y' ";
}
return str;
}
/**
* Returns an array of <code>Articles</code> for a given Category
* when the ordering is by date or headline(name). This
* method is used by the article library and does not instantiate the
* images with the article, nor does it return associated players or matches.
* Returned articles are ordered by article date descending, and a start
* index and the number of articles to retrieve must be specified.This method
* also includes the syndication categories if there exist any.
*
* @param orderBy The order by criteria - can be either name or date.
* @param categories Array of Categories for which list of articles are requested.
* If null, all categories will be included.
* @param startIndex Determines where in the result set the returned
* will start form. This is used to page through articles
* in the article library.
* The order by which results are returned. If this is set
* to <code>true</true> results will be order by headline,
* if it is <code>false</false> results will be order by
* last updated date.
* @param numberOfArticles The number of Articles that are returned.
* @param site The site for which articles are being requested.
* Only articles written by this site will be returned.
* @param excludeHomePageArticles If true, excludes Home Page articles.
* @param liveArticlesOnly restrict results to those articles which are live.
* @param excludeArticlesFor An Enumerator that specifies whether the
* articles needs to be excluded for comments or
* ratings.If null, articles will not be excluded.
* @param isDescendingSort A flag that tells whether articles needs to be
* sorted in descending order or ascending order.
* @param homePageArticlesOnly if true only home page articles are counted
* @param detailTypes Array of <code>DetailType</code> associated with the
* articles
* @param syndicationPartners List of sites by which the syndicated articles
* have to be owned by.
* @param syndicationCategories List of categories to which the syndicated
* articles have to be associated to.
* @param allSyndicatedCategories Whether to all syndicated articles from
* the syndicated partners independently of
* to which category they belong. If this
* flag is set to true the list of categories
* in the syndicationCategories parameter will
* be ignored.
* @param videoInclusion Determines whether articles with videos should be
* counted. This has one of three values:
* 1) WITH_VIDEOS only articles with videos are
* counted
* 2) WITHOUT_VIDEOS only articles without videos are
* counted
* 3) ALL_ARTICLES all articles regardless of whether
* they have videos are counted
* @param dbConnection A <code>Connection</code> to use in the method.
* @param logger A <code>Logger</code> to use in the method.
*
* @return The <code>Article</code> objects requested, or null if not found.
*
* @exception SQLException if a database access error occurs
*/
public static Article[] getArticlesforCustomArticleIndex(
int orderBy,
Category[] categories,
int startIndex,
int numberOfArticles,
Site site,
boolean excludeHomePageArticles,
boolean liveArticlesOnly,
ExcludeArticlesFor excludeArticlesFor,
boolean isDescendingSort,
boolean homePageArticlesOnly,
DetailType detailType,
Site[] syndicationPartners,
Category[] syndicationCategories,
boolean allSyndicatedCategories,
int videoInclusion,
Connection dbConnection,
Logger logger
) throws SQLException {
//
//Boolean which denotes if there are syndicated articles
//to include.
//
boolean includeSyndicatedArticles =
getIncludeSyndicatedArticles(syndicationPartners, allSyndicatedCategories,
syndicationCategories);
String queryString = "";
//
// Query for including all the categories for the
// pages selected
//
queryString =
getCategoriesAndSyndicationSQL(categories, includeSyndicatedArticles,
syndicationCategories, syndicationPartners, null);
//
//boolean to denote if the detailtype is a linked detail type
//
boolean isCategoryForLinkedDetailTypes = false;
//
// Check for if the detail type is for article or other
// linked detailtype
//
if(detailType.getId() != PageElement.DETAIL_TYPE_ARTICLE_ID
&& categories != null) {
isCategoryForLinkedDetailTypes = true;
}
//
//This method will give a filter DetailIds query for the
//given categories and detail type also if there exist
//any syndication categories then these categories will
//also be included in the query.
//
String filterDetailIdClause = Article.getFilterDetailIdClause(
site,
categories,
detailType,
syndicationPartners,
syndicationCategories,
allSyndicatedCategories);
//
// A query for excluding the articles for comments
//
String exclude_articles_with_comments_query =
"SELECT commentGroup.detail_id " +
"FROM " +
"ptvsiteconfig.comments comments, " +
"ptvsiteconfig.comment_group commentGroup, " +
"ptvdo.site_users siteuser, " +
"ptvdo.editorial_articles articles " +
"WHERE comments.comment_text is not null " +
"AND commentGroup.detail_id = articles.artl_id " +
"AND comments.comment_group_id = commentGroup.comment_group_id " +
"AND comments.live = 'Y' " +
"AND comments.is_abused = 'N' " +
"AND commentGroup.orgn_id = ? " +
Article.getVideoInclusionSQL(videoInclusion) +
"AND commentGroup.detail_type_id = ? " +
(
null != filterDetailIdClause
? "AND commentGroup.detail_id IN ( " + filterDetailIdClause + " ) "
: " "
) +
Comment.AUTHORIZATION_QUERY +
"GROUP BY commentGroup.detail_id " ;
//
// A query for excluding the articles for ratings
//
String exclude_articles_with_ratings_query =
"SELECT commentGroup.detail_id " +
"FROM " +
"ptvsiteconfig.comments comments, " +
"ptvsiteconfig.comment_group commentGroup, " +
"ptvdo.site_users siteuser " +
"WHERE comments.rating <> -1 " +
"AND comments.comment_group_id = commentGroup.comment_group_id " +
"AND comments.live = 'Y' " +
"AND comments.is_abused = 'N' " +
"AND commentGroup.orgn_id = ? " +
Article.getVideoInclusionSQL(videoInclusion) +
"AND commentGroup.detail_type_id = ? " +
(null != filterDetailIdClause ?
"AND commentGroup.detail_id IN ( " + filterDetailIdClause + " ) "
:
" ") +
Comment.AUTHORIZATION_QUERY +
"GROUP BY commentGroup.detail_id " ;
//
//Include the below query if there are categories for linked detailtype
//
if(null != excludeArticlesFor && isCategoryForLinkedDetailTypes) {
String start_article_link_query =
"SELECT " +
"DISTINCT links.artl_id " +
"FROM " +
"article_links links " +
"WHERE links.detail_type_id = ? " +
"AND links.link_id IN " +
"( ";
String end_article_link_query = ") ";
if(excludeArticlesFor == ExcludeArticlesFor.COMMENTS) {
//
//Create sql query for Excluding commented article
//
exclude_articles_with_comments_query =
start_article_link_query +
exclude_articles_with_comments_query +
end_article_link_query;
} else {
//
//Create sql query for Excluding rated article
//
exclude_articles_with_ratings_query =
start_article_link_query +
exclude_articles_with_ratings_query +
end_article_link_query;
}
}
//
// Order By clause of query
//
final String ORDER_BY_CLAUSE =
"ORDER BY " +
(orderBy == CustomArticleIndex.ORDER_BY_NAME ?
"headline" : "article_date" )+
(isDescendingSort ? " DESC " : " ASC ");
final String QUERY =
"SELECT " + Article.SQL_ARTICLE_COLUMNS +
"FROM editorial_articles a " +
"WHERE artl_id IN ( " +
"SELECT artl_id FROM ( " +
"SELECT artl_id, rownum as rnum FROM ( " +
"SELECT artl_id " +
"FROM editorial_articles " +
"WHERE " + queryString +
getExcludeHomepageArticlesSQL(
categories, excludeHomePageArticles, null) +
getLiveArticlesSQL(liveArticlesOnly) +
getVideoInclusionSQL(videoInclusion) +
getHomepageOnlySQL(homePageArticlesOnly) +
( null != excludeArticlesFor
? "AND artl_id NOT IN " +
"( " +
(excludeArticlesFor == ExcludeArticlesFor.COMMENTS
? exclude_articles_with_comments_query
: exclude_articles_with_ratings_query) +
") "
: " "
) +
ORDER_BY_CLAUSE +
") " +
") WHERE rnum BETWEEN ? AND ? " +
") " +
ORDER_BY_CLAUSE ;
logger.debug("Generated sql query for retriving articles " +
" ::: "+QUERY);
PreparedStatement query = ConnectionPool.prepareStatement(QUERY,
dbConnection,
logger
);
Article[] articles = null;
try {
int param = 1;
query.setInt(param++, site.getId());
if (categories != null && categories.length > 0) {
for (Category category : categories) {
query.setInt(param++, category.getCategoryId());
}
}
if (includeSyndicatedArticles) {
if (syndicationCategories != null && syndicationCategories.length > 0) {
for (Category syndicationCategory : syndicationCategories) {
query.setInt(param++, syndicationCategory.getCategoryId());
}
}
if (syndicationPartners != null && syndicationPartners.length >0) {
for (Site syndicationPartner : syndicationPartners) {
query.setInt(param++, syndicationPartner.getId());
}
}
}
if(null != excludeArticlesFor) {
if(isCategoryForLinkedDetailTypes){
query.setInt(param++, detailType.getId());
}
if(excludeArticlesFor == ExcludeArticlesFor.COMMENTS) {
//
// set the exclusion parameters for comments
//
query.setInt(param++, site.getId());
query.setInt(param++, detailType.getId());
} else {
//
// set the exclusion parameters for ratings
//
query.setInt(param++, site.getId());
query.setInt(param++, detailType.getId());
}
}
query.setInt(param++, startIndex);
query.setInt(param++, startIndex + numberOfArticles - 1);
//
// Construct Article objects based on the above SQL <i>without</i> images;
//
articles = Article.getArticles(query,
true,
(syndicationCategories == null ||
syndicationCategories.length == 0)
?
site
: null,
false,
dbConnection,
logger);
} finally {
query.close();
}
return articles;
}
/**
* This method returns an array of articles which are being commented.
* Note that this method only considers the live comments only and also
* checkes the authorisation of the same.
*
* @param site A <code>Site</code> instance
* @param category A <code>Category</code> instance, if not null,
* represents the articles of the supplied category needs to
* be fetched.
* @param detailType A DetailType for which articles needs to be fetched.
* This can be article detail type or any other linked
* detail type like Match, Player etc.
* @param startIndex Determines where in the result set the returned
* articles will start.
* @param detailIdsPerPage the number of articles to be returned.
* @param isDescendingOrder The order in which the articles needs to be
* retrieved.
* @param showOnlyVideoArticles Set whether or not only video articles should
* be retreived.
* @param dbConnection Database connection
* @param logger Reporting logger.
*
* @return An array of articles which are commented.
* @throws SQLException
*/
public static Article[] getCommentedArticles(Site site,
Category category,
DetailType detailType,
int startIndex,
int detailIdsPerPage,
boolean isDescendingOrder,
boolean showOnlyVideoArticles,
Connection dbConnection,
Logger logger) throws SQLException {
return getCommentedArticles(site,
new Category[] {category},
detailType,
startIndex,
detailIdsPerPage,
isDescendingOrder,
null, //Site[]indexSyndicationPartners,
null, //Category[]indexSyndicationCategories,
false, // showSyndArtsFromAllCatgs,
showOnlyVideoArticles,
dbConnection,
logger);
}
/**
* This method returns an array of articles which are being commented.
* Note that this method only considers the live comments only and also
* checkes the authorisation of the same. Also if the showOnlyVideoarticles
* is hight this method will fetch only video articles.
*
* @param site A <code>Site</code> instance
* @param categories Array of <code>Category</code> instance, if not null,
* represents the articles of the supplied category needs to
* be fetched.
* @param detailTypes Array of DetailType for which articles needs to be fetched.
* This can be article detail type or any other linked
* detail type like Match, Player etc.
* @param startIndex Determines where in the result set the returned
* articles will start.
* @param detailIdsPerPage the number of articles to be returned.
* @param isDescendingOrder The order in which the articles needs to be
* retrieved.
* @param showOnlyVideoArticles Flag if true then fetch all video articles.
* @param dbConnection Database connection
* @param logger Reporting logger.
*
* @return An array of articles which are commented.
* @throws SQLException
*/
public static Article[] getCommentedArticles(Site site,
Category[] categories,
DetailType detailTypes,
int startIndex,
int detailIdsPerPage,
boolean isDescendingOrder,
Site[]indexSyndicationPartners,
Category[]indexSyndicationCategories,
boolean showSyndArtsFromAllCatgs,
boolean showOnlyVideoArticles,
Connection dbConnection,
Logger logger) throws SQLException {
//
// The article ids fetched from comments
//
int[] detailIds = null;
int[] categoryIdsArray = null;
//
// Articles to be returned
//
Article[] articles = null;
//
//This method will give a filter DetailIds query for the
//given categories and detail type also if there exist
//any syndication categories then these categories will
//also be included in the query.
//
String filterDetailIdClause = Article.getFilterDetailIdClause(
site,
categories,
detailTypes,
indexSyndicationPartners,
indexSyndicationCategories,
showSyndArtsFromAllCatgs);
logger.debug("filterDetailIdClause SQL :: "+filterDetailIdClause);
//
//Prepare a query to fetch commented articles
//
final String QUERY =
"SELECT * " +
"FROM " +
"( " +
"SELECT " +
"cmt.*, " +
"rownum rn " +
"FROM " +
"( " +
"SELECT " +
"commentGroup.detail_id," +
"commentGroup.detail_type_id, " +
"articles.catg_id " +
"FROM " +
"ptvsiteconfig.comments comments, " +
"ptvsiteconfig.comment_group commentGroup, " +
"ptvdo.site_users siteuser, " +
"ptvdo.editorial_articles articles " +
"WHERE comments.comment_text is not null " +
"AND commentGroup.detail_id = articles.artl_id " +
"AND comments.comment_group_id = commentGroup.comment_group_id " +
"AND comments.live = 'Y' " +
"AND comments.is_abused = 'N' " +
"AND commentGroup.orgn_id = ? " +
(showOnlyVideoArticles ?
Article.getVideoInclusionSQL(Article.WITH_VIDEOS) : "" ) +
"AND commentGroup.detail_type_id = ? " +
(
null != filterDetailIdClause
? "AND commentGroup.detail_id IN ( " + filterDetailIdClause + " ) "
: " "
) +
Comment.AUTHORIZATION_QUERY +
"GROUP BY " +
"commentGroup.detail_id,commentGroup.detail_type_id,articles.catg_id " +
"ORDER BY " +
"COUNT(comments.comment_id) " +
(isDescendingOrder ? "DESC " : "ASC " ) +
") cmt " +
") " +
"WHERE rn BETWEEN ? AND ? " ;
logger.debug("Generated sql query for retriving the articles for " +
"comments ::: "+QUERY);
PreparedStatement preparedStatement = null;
try {
preparedStatement =
ConnectionPool.prepareStatement(QUERY,
ResultSet.TYPE_SCROLL_INSENSITIVE,
ResultSet.CONCUR_READ_ONLY,
dbConnection,
logger);
int param = 0;
preparedStatement.setInt(++param, site.getId());
preparedStatement.setInt(++param, detailTypes.getId());
preparedStatement.setInt(++param, startIndex);
preparedStatement.setInt(++param, startIndex + detailIdsPerPage - 1);
//
// Execute the SQL query.
//
ResultSet result =
ConnectionPool.executeQuery(preparedStatement, dbConnection, logger);
int counter = 0;
if (result.last()) {
int length = result.getRow();
categoryIdsArray = new int[length];
detailIds = new int[length];
result.beforeFirst();
//
// Iterate over the resultset and populate the
//detailIds , detailTypeIds and categoryIds array.
//
while (result.next()) {
detailIds[counter] = result.getInt("detail_id");
categoryIdsArray[counter] = result.getInt("catg_id");
counter++;
}
}
} finally {
if(null != preparedStatement) {
preparedStatement.close();
}
}
logger.debug("Fetched " +
(null == detailIds ? "0" : String.valueOf(detailIds.length)) +
" article ids ");
boolean isCategoryForLinkedDetailTypes = (null != categories
&& PageElement.DETAIL_TYPE_ARTICLE_ID != detailTypes.getId());
if(isCategoryForLinkedDetailTypes){
//
//Retrive articles for the linked detailTypes by passing the
//detail type
//
articles = Article.getArticlesFromLinkedIds(detailTypes,
detailIds,
1,
categoryIdsArray,
site,
true,
null, // TODO Include synd partners?
dbConnection,
logger);
} else{
//
//If the given detailType is not a linked detailType then
//fetch the articles using the detailIds
//
articles = Article.getArticles(detailIds,
true,
true,
site,
dbConnection,
logger);
}
logger.debug("Fetched " +
(null == articles ? "0" : String.valueOf(articles.length)) +
" article ids ");
return articles;
}
/**
* This method returns a total number of the articles
* which are being commented.
* The articles belong to the specified category, detail type
* and site. Note that this method only considers the live comments
* and also checkes the authorisation of the same.
*
* @param site A <code>Site</code> instance
* @param category A <code>Category</code> instance, if not null,
* represents the articles of the supplied category needs to
* be considered.
* @param detailType A DetailType whose for which articles needs to be
* considered. This can be article detail type or any other
* linked detail type like Match, Player etc.
* @param showOnlyVideoArticles Set whether or not only video articles should
* be retrieved.
* @param dbConnection Database connection
* @param logger Reporting logger.
*
* @return A count which specifies the total number of commented article ids
* @throws SQLException
*/
public static int getTotalCommentedArticles(Site site,
Category category,
DetailType detailType,
boolean showOnlyVideoArticles,
Connection dbConnection,
Logger logger
) throws SQLException {
return getTotalCommentedArticles(site,
new Category[]{category},
detailType,
null, //SyndicationPartners,
null, //SyndicationCategories,
false, //SyndArtsFromAllCatgs,
showOnlyVideoArticles,
dbConnection,
logger
);
}
/**
* This method returns a total number of the articles
* which are being commented.
* The articles belong to the specified category, detail type
* and site. Note that this method only considers the live comments
* and also checkes the authorisation of the same. Also if the
* showOnlyVideoarticles is hight this method will fetch only video articles.
*
* @param site A <code>Site</code> instance
* @param categories Array of <code>Category</code> instance, if not null,
* represents the articles of the supplied category needs to
* be considered.
* @param detailTypes Array of DetailType for which articles needs to be
* considered. This can be article detail type or any other
* linked detail type like Match, Player etc.
* @param showOnlyVideoArticles Flag if true then fetch all video articles only.
* @param dbConnection Database connection
* @param logger Reporting logger.
*
* @return A count which specifies the total number of commented article ids
* @throws SQLException
*/
public static int getTotalCommentedArticles(Site site,
Category[] categories,
DetailType detailType,
Site[]indexSyndicationPartners,
Category[]indexSyndicationCategories,
boolean showSyndArtsFromAllCatgs,
boolean showOnlyVideoArticles,
Connection dbConnection,
Logger logger
) throws SQLException {
//
// The total count to be returned
//
int totalCount = 0;
//
//This method will give a filter DetailIds query for the
//given categories and detail type also if there exist
//any syndication categories then these categories will
//also be included in the query.
//
String filterDetailIdClause = Article.getFilterDetailIdClause(
site,
categories,
detailType,
indexSyndicationPartners,
indexSyndicationCategories,
showSyndArtsFromAllCatgs);
//
//Create a SQL query to count the total number of
//commented articles.
//
final String QUERY =
"SELECT COUNT(*) " +
"FROM " +
"( " +
"SELECT " +
"commentGroup.detail_id " +
"FROM " +
"ptvsiteconfig.comments comments, " +
"ptvsiteconfig.comment_group commentGroup, " +
"ptvdo.site_users siteuser, " +
"ptvdo.editorial_articles articles " +
"WHERE comments.comment_text is not null " +
"AND commentGroup.detail_id = articles.artl_id " +
"AND comments.comment_group_id = commentGroup.comment_group_id " +
"AND comments.live = 'Y' " +
"AND comments.is_abused = 'N' " +
"AND commentGroup.orgn_id = ? " +
(showOnlyVideoArticles ? getVideoInclusionSQL(Article.WITH_VIDEOS, "articles.") : "") +
"AND commentGroup.detail_type_id = ? " +
(
null != filterDetailIdClause
? "AND commentGroup.detail_id IN ( " + filterDetailIdClause + " ) "
: " "
) +
Comment.AUTHORIZATION_QUERY +
"GROUP BY " +
"commentGroup.detail_id " +
") " ;
logger.debug("Generated sql query for retriving the total number of " +
"articles for comments ::: "+QUERY);
PreparedStatement preparedStatement = null;
try {
preparedStatement =
ConnectionPool.prepareStatement(QUERY,
ResultSet.TYPE_SCROLL_INSENSITIVE,
ResultSet.CONCUR_READ_ONLY,
dbConnection,
logger);
int param = 0;
preparedStatement.setInt(++param, site.getId());
preparedStatement.setInt(++param, detailType.getId());
//
// Execute the SQL query.
//
ResultSet result =
ConnectionPool.executeQuery(preparedStatement, dbConnection, logger);
if (result.next()) {
totalCount = result.getInt(1);
}
} finally {
if(null != preparedStatement) {
preparedStatement.close();
}
}
logger.debug("Number of commented articles :: " + totalCount);
return totalCount;
}
/**
* This method returns an array of articles which are being rated.
* Note that this method only considers the live comments only and also
* checkes the authorisation of the same.
*
* @param site A <code>Site</code> instance
* @param category A <code>Category</code> instance, if not null,
* represents the articles of the supplied category needs to
* be fetched.
* @param detailType A DetailType whose for which articles needs to be fetched.
* This can be article detail type or any other linked
* detail type like Match, Player etc.
* @param startIndex Determines where in the result set the returned
* articles will start.
* @param detailIdsPerPage the number of articles to be returned.
* @param isDescendingOrder The order in which the articles needs to be
* retrieved.
* @param showOnlyVideoArticles Set whether or not only video articles should
* be retrieved.
* @param dbConnection Database connection
* @param logger Reporting logger.
*
* @return An array of articles which are commented.
* @throws SQLException
*/
public static Article[] getRatedArticles(Site site,
Category category,
DetailType detailType,
int startIndex,
int detailIdsPerPage,
boolean isDescendingOrder,
boolean showOnlyVideoArticles,
Connection dbConnection,
Logger logger) throws SQLException {
return getRatedArticles(site,
new Category[]{category},
detailType,
startIndex,
detailIdsPerPage,
isDescendingOrder,
null, //SyndicationPartners,
null, //SyndicationCategories,
false, //showSyndArtsFromAllCatgs,
showOnlyVideoArticles,
dbConnection,
logger);
}
/**
* This method returns an array of articles which are being rated.
* Note that this method only considers the live comments only and also
* checkes the authorisation of the same.Also if the showOnlyVideoarticles
* is hight this method will fetch only video articles.
*
* @param site A <code>Site</code> instance
* @param categories Array of <code>Category</code> instance, if not null,
* represents the articles of the supplied category needs to
* be fetched.
* @param detailTypes Array of DetailType whose for which articles needs to be
* fetched.This can be article detail type or any other linked
* detail type like Match, Player etc.
* @param startIndex Determines where in the result set the returned
* articles will start.
* @param detailIdsPerPage the number of articles to be returned.
* @param isDescendingOrder The order in which the articles needs to be
* retrieved.
* @param showOnlyVideoArticles Flag if true then fetch all video articles only.
* @param dbConnection Database connection
* @param logger Reporting logger.
*
* @return An array of articles which are commented.
* @throws SQLException
*/
public static Article[] getRatedArticles(Site site,
Category[] categories,
DetailType detailType,
int startIndex,
int detailIdsPerPage,
boolean isDescendingOrder,
Site[]indexSyndicationPartners,
Category[]indexSyndicationCategories,
boolean showSyndArtsFromAllCatgs,
boolean showOnlyVideoArticles,
Connection dbConnection,
Logger logger) throws SQLException {
//
// The article ids fetched from ratings
// and the respective detailtypeids and
// categoryids
//
int[] detailIds = null;
int[] categoryIdsArray = null;
//
// Articles to be returned
//
Article[] articles = null;
//
//This method will give a filter DetailIds query for the
//given categories and detail type also if there exist
//any syndication categories then these categories will
//also be included in the query.
//
String filterDetailIdClause = Article.getFilterDetailIdClause(
site,
categories,
detailType,
indexSyndicationPartners,
indexSyndicationCategories,
showSyndArtsFromAllCatgs);
final String QUERY =
"SELECT * " +
"FROM " +
"( " +
"SELECT " +
"cmt.*, " +
"rownum rn " +
"FROM " +
"( " +
"SELECT " +
"commentGroup.detail_id, " +
"commentGroup.detail_type_id, " +
"articles.catg_id " +
"FROM " +
"ptvsiteconfig.comments comments, " +
"ptvsiteconfig.comment_group commentGroup, " +
"ptvdo.site_users siteuser, " +
"ptvdo.editorial_articles articles " +
"WHERE comments.rating <> -1 " +
"AND commentGroup.detail_id = articles.artl_id " +
"AND comments.comment_group_id = commentGroup.comment_group_id " +
"AND comments.live = 'Y' " +
"AND comments.is_abused = 'N' " +
"AND commentGroup.orgn_id = ? " +
(showOnlyVideoArticles ?
Article.getVideoInclusionSQL(Article.WITH_VIDEOS, "articles.") : "" ) +
"AND commentGroup.detail_type_id = ? "
+
(
null != filterDetailIdClause
? "AND commentGroup.detail_id IN ( " + filterDetailIdClause + " ) "
: " "
) +
Comment.AUTHORIZATION_QUERY +
"GROUP BY " +
"commentGroup.detail_id, commentGroup.detail_type_id, articles.catg_id " +
"ORDER BY " +
"AVG(comments.rating) " +
(isDescendingOrder ? "DESC " : "ASC " ) +
") cmt " +
") " +
"WHERE rn BETWEEN ? AND ? " ;
logger.debug("Generated sql query for retriving the articles for " +
"ratings ::: "+QUERY);
PreparedStatement preparedStatement = null;
try {
preparedStatement =
ConnectionPool.prepareStatement(QUERY,
ResultSet.TYPE_SCROLL_INSENSITIVE,
ResultSet.CONCUR_READ_ONLY,
dbConnection,
logger);
int param = 0;
preparedStatement.setInt(++param, site.getId());
preparedStatement.setInt(++param, detailType.getId());
preparedStatement.setInt(++param, startIndex);
preparedStatement.setInt(++param, startIndex + detailIdsPerPage - 1);
//
// Execute the SQL query.
//
ResultSet result =
ConnectionPool.executeQuery(preparedStatement, dbConnection, logger);
int counter = 0;
if (result.last()) {
int length = result.getRow();
detailIds = new int[length] ;
categoryIdsArray = new int[length];
result.beforeFirst();
while (result.next()) {
detailIds[counter] = result.getInt("detail_id");
categoryIdsArray[counter] = result.getInt("catg_id");
counter++;
}
}
} finally {
if(null != preparedStatement) {
preparedStatement.close();
}
}
logger.debug("Fetched " +
(null == detailIds ? "0" : String.valueOf(detailIds.length)) +
" article ids ");
//
//Check if the categories are for linked detatType.
//
boolean isCategoryForLinkedDetailTypes = (null != categories
&& PageElement.DETAIL_TYPE_ARTICLE_ID != detailType.getId());
if(isCategoryForLinkedDetailTypes){
//
//Retrive articles for the linked detailTypes by passing the
//detail type id, link ids and category Ids
//
articles = Article.getArticlesFromLinkedIds(
detailType,
detailIds,
1,
categoryIdsArray,
site,
true,
null, // TODO Include synd partners?
dbConnection,
logger);
} else{
//
//If the given detailType is not a linked detailType then
//fetch the articles using the detailIds
//
articles = Article.getArticles(detailIds,
true,
true,
site,
dbConnection,
logger);
}
logger.debug("Fetched " +
(null == articles ? "0" : String.valueOf(articles.length)) +
" article ids ");
return articles;
}
/**
* This method returns a total number of the articles which are being rated
* The articles belong to the specified category, detail type
* and site.
*
* @param site A <code>Site</code> instance
* @param category A <code>Category</code> instance, if not null,
* represents the articles of the supplied category needs to
* be considered.
* @param detailType A DetailType whose for which articles needs to be
* considered. This can be article detail type or any other
* linked detail type like Match, Player etc.
* @param showOnlyVideoArticles Set whether or not only video articles should
* be retrieved.
* @param dbConnection Database connection
* @param logger Reporting logger.
*
* @return A count which specifies the total number of rated article ids
* @throws SQLException
*/
public static int getTotalRatedArticles(Site site,
Category category,
DetailType detailType,
boolean showOnlyVideoArticles,
Connection dbConnection,
Logger logger
) throws SQLException {
return getTotalRatedArticles(site,
new Category[] {category},
detailType,
null, //SyndicationPartners,
null, //SyndicationCategories,
false, //showSyndArtsFromAllCatgs,
showOnlyVideoArticles,
dbConnection,
logger
);
}
/**
* This method returns a total number of the articles which are being rated
* The articles belong to the specified category, detail type
* and site.
*
* @param site A <code>Site</code> instance
* @param categories Array of <code>Category</code> instance, if not null,
* represents the articles of the supplied category needs to
* be considered.
* @param detailTypes Array of DetailType whose for which articles needs to be
* considered. This can be article detail type or any other
* linked detail type like Match, Player etc.
* @param showOnlyVideoArticles Flag if true then fetch all video articles only.
* @param dbConnection Database connection
* @param logger Reporting logger.
*
* @return A count which specifies the total number of rated article ids
* @throws SQLException
*/
public static int getTotalRatedArticles(Site site,
Category[] categories,
DetailType detailType,
Site[]indexSyndicationPartners,
Category[]indexSyndicationCategories,
boolean showSyndArtsFromAllCatgs,
boolean showOnlyVideoArticles,
Connection dbConnection,
Logger logger
) throws SQLException {
//
// The total count to be returned
//
int totalCount = 0;
//
//This method will give a filter DetailIds query for the
//given categories and detail type also if there exist
//any syndication categories then these categories will
//also be included in the query.
//
String filterDetailIdClause = Article.getFilterDetailIdClause(
site,
categories,
detailType,
indexSyndicationPartners,
indexSyndicationCategories,
showSyndArtsFromAllCatgs
);
final String QUERY =
"SELECT COUNT(*) " +
"FROM " +
"( " +
"SELECT " +
"commentGroup.detail_id " +
"FROM " +
"ptvsiteconfig.comments comments, " +
"ptvsiteconfig.comment_group commentGroup, " +
"ptvdo.site_users siteuser, " +
"ptvdo.editorial_articles articles " +
"WHERE comments.rating <> -1 " +
"AND commentGroup.detail_id = articles.artl_id " +
"AND comments.comment_group_id = commentGroup.comment_group_id " +
"AND comments.live = 'Y' " +
"AND comments.is_abused = 'N' " +
"AND commentGroup.orgn_id = ? " +
(showOnlyVideoArticles ?
Article.getVideoInclusionSQL(Article.WITH_VIDEOS, "articles.") : "") +
"AND commentGroup.detail_type_id = ? " +
(
null != filterDetailIdClause
? "AND commentGroup.detail_id IN ( " + filterDetailIdClause + " ) "
: " "
) +
Comment.AUTHORIZATION_QUERY +
"GROUP BY " +
"commentGroup.detail_id " +
") " ;
logger.debug("Generated sql query for retriving the total number of " +
"article ids for ratings ::: "+QUERY);
PreparedStatement preparedStatement = null;
try {
preparedStatement =
ConnectionPool.prepareStatement(QUERY,
ResultSet.TYPE_SCROLL_INSENSITIVE,
ResultSet.CONCUR_READ_ONLY,
dbConnection,
logger);
int param = 0;
preparedStatement.setInt(++param, site.getId());
preparedStatement.setInt(++param, detailType.getId());
//
// Execute the SQL query.
//
ResultSet result =
ConnectionPool.executeQuery(preparedStatement, dbConnection, logger);
if (result.next()) {
totalCount = result.getInt(1);
}
} finally {
if(null != preparedStatement) {
preparedStatement.close();
}
}
logger.debug("Number of rated articles :: " + totalCount);
return totalCount;
}
/**
* This method is use to get comma seperated categoryIds
* from the array of Category.If the categories are null
* append -99 to the buffer so that the IN clause dose not
* get null string.
*
* @param categoreis Array of Category instances.
* @return String comma seperated categoryIds
*/
public static String getCommaSeperatedCategories(Category[] categories){
StringBuffer catBuff = new StringBuffer("");
if(null != categories && categories.length > 0){
catBuff = new StringBuffer(categories[0] == null ? "" + (-99) : "?");
for(int i=1; i < categories.length; i++){
catBuff.append(",");
if(null != categories[i]){
catBuff.append("?");
}else{
catBuff.append("-99");
}
}
}
return catBuff.toString();
}
/**
* Return live articles which share any of this articles keywords. Articles
* are, returned grouped by category, prioritising several ptv categories
* first.
*
* @param numberOfArticlesToReturn Maximum number of articles to return
* @param instantiateImages Specifieds whether images are instantiated
* @param onlySVAVideoArticles If this flag is set to true, only related
* articles that are linked to at least one SVA video will be returned.
* @param videoTypeIdArray If not null and the "onlySVAVideoArticles" flag is
* set to true, only related articles that are linked to at least one
* SVA video of one of the video type ids included in this array will
* be returned.
* @param dbConnection Database connection
* @param logger Logger for reporting
*
* @return An array of articles not greater than the maximum length specified
* containing articles relating to the keywords
*
* @throws SQLException on database error
*/
public Article[] getRelatedArticles(int numberOfArticlesToReturn,
int noOfDays,
boolean instantiateImages,
boolean onlySVAVideoArticles,
int[] videoTypeIdArray,
Connection dbConnection,
Logger logger)
throws SQLException {
return this.getRelatedArticles(PageElement.NO_DETAIL_ID,
numberOfArticlesToReturn,
noOfDays,
instantiateImages,
onlySVAVideoArticles,
videoTypeIdArray,
null,
null,
dbConnection, logger);
}
/**
* Return live articles which share any of the specified keywords. Articles
* are, returned grouped by category, prioritising several ptv categories
* first.
*
* @param numberOfArticlesToReturn Maximum number of articles to return
* @param instantiateImages Specifieds whether images are instantiated
* @param onlySVAVideoArticles If this flag is set to true, only related
* articles that are linked to at least one SVA video will be returned.
* @param videoTypeIdArray If not null and the "onlySVAVideoArticles" flag is
* set to true, only related articles that are linked to at least one
* SVA video of one of the video type ids included in this array will
* be returned.
* @param category If present only articles belonging to this category are
* returned - otherwise all categories are included
* @param dbConnection Database connection
* @param logger Logger for reporting
*
* @return An array of articles not greater than the maximum length specified
* containing articles relating to the keywords
*
* @throws SQLException on database error
*/
public Article[] getRelatedArticles(int numberOfArticlesToReturn,
int noOfDays,
boolean instantiateImages,
boolean onlySVAVideoArticles,
int[] videoTypeIdArray,
Category category,
Connection dbConnection,
Logger logger)
throws SQLException {
return getRelatedArticles(numberOfArticlesToReturn,
-1, //No maximum number of articles per category.
noOfDays,
instantiateImages,
onlySVAVideoArticles,
videoTypeIdArray,
(category != null ? new Category[] {category} : null),
null,
dbConnection, logger);
}
/**
* Return live articles which share any of the specified keywords. Articles
* are, returned grouped by category, prioritising several ptv categories
* first and ordering by rank by default.
*
* @param numberOfArticlesToReturn
* Maximum number of articles to return
* @param maximumArticlesPerCategory
* Maximum number of articles per each category.
* @param instantiateImages
* Specifieds whether images are instantiated
* @param onlySVAVideoArticles If this flag is set to true, only related
* articles that are linked to at least one SVA video will be returned.
* @param videoTypeIdArray If not null and the "onlySVAVideoArticles" flag is
* set to true, only related articles that are linked to at least one
* SVA video of one of the video type ids included in this array will
* be returned.
* @param categories
* If present only articles belonging to these categories are returned -
* otherwise all categories are included
* @param detailTypeCategories TODO
* @param dbConnection
* Database connection
* @param logger
* Logger for reporting
* @return An array of articles not greater than the maximum length specified
* containing articles relating to the keywords
*
* @throws SQLException
* on database error
*/
public Article[] getRelatedArticles(int numberOfArticlesToReturn,
int maximumArticlesPerCategory,
int noOfDays,
boolean instantiateImages,
boolean onlySVAVideoArticles,
int[] videoTypeIdArray,
Category[] categories,
int[] detailTypeCategories,
Connection dbConnection, Logger logger) throws SQLException {
return getRelatedArticles(numberOfArticlesToReturn,
maximumArticlesPerCategory,
noOfDays,
instantiateImages,
categories,
MulticategoryArticleIndex.ORDER_BY_RANKING,
onlySVAVideoArticles,
videoTypeIdArray,
false, // not linking by linked detail objects
null, // no detail type id
PageElement.NO_DETAIL_ID, // no detail id
detailTypeCategories,
true, // backwards compatibility ensuring this article is always filtered out
dbConnection,
logger);
}
/**
* Generate the SQL query used to return a collection of articles that are
* part of the required categories.
*
* @param numberOfArticlesToReturn The maximum number of articles to return.
* @param maximumArticlesPerCategory The maximum number of articles to include
* for each category.
* @param noOfDays
* @param categories If present only articles belonging to these categories
* will be queried for.
* @param orderBy How to order the results (name, date, rank).
* @param onlySVAVideoArticles If this flag is set to true, only related
* articles that are linked to at least one SVA video will be returned.
* @param videoTypeIdArray If not null and the "onlySVAVideoArticles" flag is
* set to true, only related articles that are linked to at least one
* SVA video of one of the video type ids included in this array will
* be returned.
* @param detailTypeCategories
* @param filterOutThisArticleFromResults if true then the current article is
* not returned in the results, this is usually if the related articles
* is on an article page, if the element is on a match or player page
* then this will be set to false so that all articles are returned.
* @param logger The Logger to use for reporting.
* @return The SQL query to use to to get the collection of related articles.
*/
private String constructSelectForRelatedArticlesSQL(
int numberOfArticlesToReturn,
int maximumArticlesPerCategory,
int noOfDays,
Category[] categories,
int orderBy,
boolean onlySVAVideoArticles,
int[] videoTypeIdArray,
boolean onlyLinkedObjectArticles,
DetailType detailType,
int detailId,
int[] detailTypeCategories,
boolean filterOutThisArticleFromResults,
Collection<Site> syndicationPartners,
Logger logger) {
final boolean useSyndication = (syndicationPartners != null) && !syndicationPartners.isEmpty();
//
// Create category IDs list
//
StringBuilder categoryIds = null;
if (categories != null && categories.length > 0) {
categoryIds = new StringBuilder();
for (int ii = 0; ii < categories.length; ii++) {
categoryIds.append((ii == 0 ? "" : ",") + "?");
}
}
StringBuilder sqlQuery = new StringBuilder();
if(maximumArticlesPerCategory > 0) {
sqlQuery.append("SELECT * FROM ( ");
}
sqlQuery.append("SELECT ").append(Article.SQL_ARTICLE_COLUMNS);
sqlQuery.append(",ea_rank.rank_order ");
if(maximumArticlesPerCategory > 0) {
sqlQuery.append(",categoryRank ");
}
sqlQuery.append("FROM ( ");
sqlQuery.append("SELECT artl_id, article_date, min(keyword_rank) rank_order ");
if(maximumArticlesPerCategory > 0) {
sqlQuery.append(",categoryRank ");
}
sqlQuery.append("FROM ( ");
sqlQuery.append("SELECT a.artl_id,a.headline,a.article_date, ak.rank AS keyword_rank ");
if(maximumArticlesPerCategory > 0) {
sqlQuery.append(", dense_rank() over (partition by catg_id order by article_date desc, a.artl_id desc) categoryRank ");
}
sqlQuery.append("FROM editorial_articles a , article_keywords ak ");
if(!ArrayUtils.isEmpty(detailTypeCategories)) {
sqlQuery.append(", detail_type_instance_category dtic ");
}
sqlQuery.append("WHERE a.artl_id = ak.artl_id ");
sqlQuery.append("AND ak.artl_id IN ( ");
if(onlyLinkedObjectArticles) {
sqlQuery.append("SELECT DISTINCT(artl_id) FROM article_links WHERE detail_type_id = ? and link_id = ? ");
} else {
sqlQuery.append("SELECT DISTINCT(artl_id) ");
sqlQuery.append("FROM article_keywords ");
if(useSyndication) {
sqlQuery.append("WHERE orgn_id IN (?,");
sqlQuery.append(ConnectionPool.getTupleInList(syndicationPartners.size(), "?"));
sqlQuery.append(") ");
} else {
sqlQuery.append("WHERE orgn_id = ? ");
}
if(filterOutThisArticleFromResults) {
sqlQuery.append("AND artl_id <> ? ");
}
sqlQuery.append("AND keyword IN ( ");
sqlQuery.append("SELECT keyword ");
sqlQuery.append("FROM article_keywords ");
sqlQuery.append("WHERE artl_id = ? ");
sqlQuery.append("AND orgn_id = ? ");
sqlQuery.append(") ");
if(onlySVAVideoArticles) {
sqlQuery.append(" AND a.artl_id IN (SELECT article_id FROM article_videos ");
if(!ArrayUtils.isEmpty(videoTypeIdArray)) {
sqlQuery.append(" WHERE video_id IN ( SELECT video_id FROM VIDEOS WHERE video_type_id IN (");
sqlQuery.append(ConnectionPool.getInList(videoTypeIdArray));
sqlQuery.append(") ) ");
}
sqlQuery.append(") ");
}
}
sqlQuery.append(") ");
if(noOfDays > 0) {
sqlQuery.append(" AND a.article_date > (SYSDATE - ?) ");
}
if(categoryIds != null) {
sqlQuery.append(" AND catg_id IN (");
sqlQuery.append(categoryIds);
sqlQuery.append(") ");
}
// Including the org ID here improves query response time significantly.
if(useSyndication) {
sqlQuery.append("AND ((a.orgn_club_id = ?) OR (a.syndicated_flg='Y' AND (a.orgn_club_id IN (");
sqlQuery.append(ConnectionPool.getInList(syndicationPartners));
sqlQuery.append("))))");
} else {
sqlQuery.append("AND a.orgn_club_id = ? ");
}
sqlQuery.append(LIVE_ARTICLES_SQL);
//
//Checks if detailTypeCategories were passed.
//
if (detailTypeCategories != null && detailTypeCategories.length > 0) {
//
//Filter by the list of detail type categories, if passed.
//
sqlQuery.append(" AND a.artl_id = dtic.DETAIL_INSTANCE_ID(+) ");
sqlQuery.append(" AND dtic.DETAIL_TYPE_ID(+) = 1 ");
sqlQuery.append(" AND (dtic.EXPIRATION_DATE IS NULL OR SYSDATE < dtic.EXPIRATION_DATE) ");
sqlQuery.append(getExistsStatementsByDetailTypeCategories(detailTypeCategories));
}
sqlQuery.append(") GROUP BY artl_id, article_date ");
if(maximumArticlesPerCategory > 0) {
sqlQuery.append(",categoryRank ");
}
sqlQuery.append(") ea_rank, editorial_articles a ");
sqlQuery.append("WHERE ea_rank.artl_id = a.artl_id ");
switch(orderBy) {
case MulticategoryArticleIndex.ORDER_BY_NAME:
sqlQuery.append(" ORDER BY a.headline ASC ");
break;
case MulticategoryArticleIndex.ORDER_BY_NAME_DESC:
sqlQuery.append(" ORDER BY a.headline DESC ");
break;
case MulticategoryArticleIndex.ORDER_BY_MOST_RECENT:
sqlQuery.append(" ORDER BY a.article_date DESC ");
break;
case MulticategoryArticleIndex.ORDER_BY_RANKING:
sqlQuery.append("ORDER BY rank_order ASC,DECODE(a.catg_id, ");
sqlQuery.append(Category.WORLD_TV_CATEGORY_ID);
sqlQuery.append(", 1, ");
sqlQuery.append(Category.WORLD_RADIO_CATEGORY_ID);
sqlQuery.append(", 2, ");
sqlQuery.append(Category.WORLD_INTERVIEWS_CATEGORY_ID);
sqlQuery.append(", 3, a.catg_id ");
sqlQuery.append(") ASC, a.article_date DESC ");
break;
default:
// Do nothing
}
if(maximumArticlesPerCategory > 0) {
sqlQuery.append(") WHERE categoryRank <= ? ");
}
return sqlQuery.toString();
}
private String getExistsStatementsByDetailTypeCategories(int[] detailTypeCategories) {
StringBuffer query = new StringBuffer("");
query.append(" AND (");
for (int a = 0; a < detailTypeCategories.length; a++){
if (a != 0){
query.append(" AND ");
}
query.append(" (EXISTS (SELECT detail_type_id FROM detail_type_instance_category WHERE category_id = ? AND detail_type_id = 1 AND (expiration_date IS NULL OR expiration_date > sysdate) AND DETAIL_INSTANCE_ID = dtic.DETAIL_INSTANCE_ID)) ");
}
query.append(")");
return query.toString();
}
/**
* Bind the required SQL query parameters into the query for related articles.
*
* @param query The SQL query to
* @param numberOfArticlesToReturn The maximum number of articles to allow.
* @param maximumArticlesPerCategory The maximum number of articles to include
* for each category.
* @param noOfDays
* @param categories If present only articles belonging to these categories
* will be queried for.
* @param orderBy How to order the results (name, date, rank).
* @param onlySVAVideoArticles If this flag is set to true, only related
* articles that are linked to at least one SVA video will be returned.
* @param videoTypeIdArray If not null and the "onlySVAVideoArticles" flag is
* set to true, only related articles that are linked to at least one
* SVA video of one of the video type ids included in this array will
* be returned.
* @param detailTypeCategories TODO
* @param filterOutThisArticleFromResults if true then the current article is
* not returned in the results, this is usually if the related articles
* is on an article page, if the element is on a match or player page
* then this will be set to false so that all articles are returned.
* @param dbConnection The database connection to use.
* @param logger The Logger to use for reporting.
* @return The position of the LAST bind parameter used. ie if eight
* parameters were bound to the query, this would return 8.
*
* @throws SQLException If a problem occurred attempting to bind the query
* parameters.
*/
private int bindRelatedArticlesParams(PreparedStatement query,
int numberOfArticlesToReturn,
int maximumArticlesPerCategory,
int noOfDays,
Category[] categories,
int orderBy,
boolean onlySVAVideoArticles,
int[] videoTypeIdArray,
boolean onlyLinkedObjectArticles,
DetailType detailType,
int detailId,
int[] detailTypeCategories,
boolean filterOutThisArticleFromResults,
Collection<Site> syndicationPartners,
Connection dbConnection,
Logger logger)
throws SQLException {
final boolean useSyndication = (syndicationPartners != null) && !syndicationPartners.isEmpty();
int param = 0;
if(onlyLinkedObjectArticles) {
query.setInt(++param, detailType.getId());
query.setInt(++param, detailId);
} else {
query.setInt(++param, this.usedBy.getId());
if(useSyndication) {
for(Site syndPartner: syndicationPartners) {
query.setInt(++param, syndPartner.getId());
}
}
//
// only need to add this parameter if we are filtering out the current article
//
if(filterOutThisArticleFromResults) {
query.setInt(++param, this.articleId);
}
query.setInt(++param, this.articleId);
if(useSyndication) {
query.setInt(++param, this.ownedBy.getId());
} else {
query.setInt(++param, this.usedBy.getId());
}
if (onlySVAVideoArticles && videoTypeIdArray != null && videoTypeIdArray.length > 0) {
param = ConnectionPool.setInListParameters(query, param, videoTypeIdArray);
}
}
if (0 < noOfDays) {
query.setInt(++param, noOfDays);
}
if (categories != null) {
for (int jj = 0; jj < categories.length; ++jj) {
query.setInt(++param, categories[jj].getCategoryId());
}
}
// Set the organisation and syndication partners (if specified)
query.setInt(++param, this.usedBy.getId());
if(useSyndication) {
for(Site syndicationPartner: syndicationPartners) {
query.setInt(++param, syndicationPartner.getId());
}
}
//
//Checks if Detail Type Categories were passed.
//
if (!ArrayUtils.isEmpty(detailTypeCategories)){
for (int a = 0; a < detailTypeCategories.length; a++){
query.setInt(++param, detailTypeCategories[a]);
}
}
if (maximumArticlesPerCategory > 0) {
query.setInt(++param, maximumArticlesPerCategory);
}
return param;
}
/**
* Return the number of live articles which share any of the specified
*
* @param maximumArticlesPerCategory The maximum number of articles to include
* for each category.
* @param noOfDays
* @param categories If present only articles belonging to these categories
* will be queried for.
* @param orderBy How to order the results (name, date, rank).
* @param onlySVAVideoArticles If this flag is set to true, only related
* articles that are linked to at least one SVA video will be returned.
* @param videoTypeIdArray If not null and the "onlySVAVideoArticles" flag is
* set to true, only related articles that are linked to at least one
* SVA video of one of the video type ids included in this array will
* be returned.
* @param filterOutThisArticleFromResults if true then the current article is
* not returned in the results, this is usually if the related articles
* is on an article page, if the element is on a match or player page
* then this will be set to false so that all articles are returned.
* @param syndicationPartners The {@link Collection} of {@link Site} syndication
* partners to check for content in as well.
* @param dbConnection The database connection to use.
* @param logger The Logger to use for reporting.
*
* @return The number of related articles that match the specified categories.
*
* @throws SQLException If a problem occurred while querying the database.
*/
public int getRelatedArticlesCount(int maximumArticlesPerCategory,
int noOfDays,
Category[] categories,
int orderBy,
boolean onlySVAVideoArticles,
int[] videoTypeIdArray,
boolean onlyLinkedObjectArticles,
DetailType detailType,
int detailId,
boolean filterOutThisArticleFromResults,
Collection<Site> syndicationPartners,
Connection dbConnection,
Logger logger)
throws SQLException {
int relatedArticles = 0;
StringBuilder sqlQuery = new StringBuilder("SELECT COUNT(*) as article_count FROM (");
sqlQuery.append(constructSelectForRelatedArticlesSQL(
0,
maximumArticlesPerCategory,
noOfDays,
categories,
orderBy,
onlySVAVideoArticles,
videoTypeIdArray,
onlyLinkedObjectArticles,
detailType,
detailId,
null,
filterOutThisArticleFromResults,
syndicationPartners,
logger));
sqlQuery.append(')');
PreparedStatement query = ConnectionPool.prepareStatement(
sqlQuery.toString(),
dbConnection,
logger
);
try {
bindRelatedArticlesParams(query,
0,
maximumArticlesPerCategory,
noOfDays,
categories,
orderBy,
onlySVAVideoArticles,
videoTypeIdArray,
onlyLinkedObjectArticles,
detailType,
detailId,
null,
filterOutThisArticleFromResults,
syndicationPartners,
dbConnection,
logger);
ResultSet rs = ConnectionPool.executeQuery(query, dbConnection, logger);
if(rs.next()) {
relatedArticles = rs.getInt("article_count");
}
} finally {
query.close();
}
return relatedArticles;
}
/**
* Return the list of articles related to this article, ordered by name, date
* or rank. This will retrieve the requested number of articles starting at
* the specified offset into the query results.
*
* @param startPosition The offset into the result set to start retrieving
* @param numberOfArticlesToReturn The maximum number of articles to allow.
* @param numberOfArticlesToReturn
* @param maximumArticlesPerCategory The maximum number of articles to include
* for each category.
* @param maximumArticlesPerCategory
* @param noOfDays
* @param noOfDays
* @param instantiateImages
* @param categories If present only articles belonging to these categories
* will be queried for.
* @param categories
* @param orderBy How to order the results (name, date, rank).
* @param orderBy
* @param onlySVAVideoArticles If this flag is set to true, only related
* articles that are linked to at least one SVA video will be returned.
* @param videoTypeIdArray If not null and the "onlySVAVideoArticles" flag is
* set to true, only related articles that are linked to at least one
* SVA video of one of the video type ids included in this array will
* be returned.
* @param detailTypeCategories
* @param filterOutThisArticleFromResults if true then the current article is
* not returned in the results, this is usually if the related articles
* is on an article page, if the element is on a match or player page
* then this will be set to false so that all articles are returned.
* @param dbConnection The database connection to use.
* @param dbConnection
* @param logger The Logger to use for reporting.
* @param logger
* @return
*/
public Article[] getRelatedArticles(int startPosition,
int numberOfArticlesToReturn,
int maximumArticlesPerCategory,
int noOfDays,
boolean instantiateImages,
Category[] categories,
int orderBy,
boolean onlySVAVideoArticles,
int[] videoTypeIdArray,
boolean onlyLinkedObjectArticles,
DetailType detailType,
int detailId,
int[] detailTypeCategories,
boolean filterOutThisArticleFromResults,
Collection<Site> syndicationPartners,
Connection dbConnection, Logger logger)
throws SQLException {
Article[] articles = null;
StringBuilder sqlQuery = new StringBuilder("select * from (");
sqlQuery.append("select ROWNUM as rnum, r.* from (");
sqlQuery.append(constructSelectForRelatedArticlesSQL(
numberOfArticlesToReturn,
maximumArticlesPerCategory,
noOfDays,
categories,
orderBy,
onlySVAVideoArticles,
videoTypeIdArray,
onlyLinkedObjectArticles,
detailType,
detailId,
detailTypeCategories,
filterOutThisArticleFromResults,
syndicationPartners,
logger));
sqlQuery.append(") r where rownum < ?) where rnum >= ? ");
PreparedStatement query = ConnectionPool.prepareStatement(
sqlQuery.toString(),
dbConnection,
logger
);
try {
int param = bindRelatedArticlesParams(query,
numberOfArticlesToReturn,
maximumArticlesPerCategory,
noOfDays,
categories,
orderBy,
onlySVAVideoArticles,
videoTypeIdArray,
onlyLinkedObjectArticles,
detailType,
detailId,
detailTypeCategories,
filterOutThisArticleFromResults,
syndicationPartners,
dbConnection,
logger);
query.setInt(++param, startPosition + numberOfArticlesToReturn);
query.setInt(++param, startPosition);
articles = Article.getArticles(query,
instantiateImages,
this.usedBy,
false,
dbConnection,
logger);
} finally {
query.close();
}
return articles;
}
/**
* Return live articles which share any of the specified keywords. Articles
* are, returned grouped by category, prioritising several ptv categories
* first.
*
* @param numberOfArticlesToReturn
* Maximum number of articles to return
* @param maximumArticlesPerCategory
* Maximum number of articles per each category.
* @param instantiateImages
* Specifieds whether images are instantiated
* @param categories
* If present only articles belonging to these categories are returned -
* otherwise all categories are included
* @param orderBy
* How to order the results (name, date, rank)
* @param onlySVAVideoArticles If this flag is set to true, only related
* articles that are linked to at least one SVA video will be returned.
* @param videoTypeIdArray If not null and the "onlySVAVideoArticles" flag is
* set to true, only related articles that are linked to at least one
* SVA video of one of the video type ids included in this array will
* be returned.
* @param detailTypeCategories TODO
* @param filterOutThisArticleFromResults if true then the current article is
* not returned in the results, this is usually if the related articles
* is on an article page, if the element is on a match or player page
* then this will be set to false so that all articles are returned.
* @param dbConnection
* Database connection
* @param logger
* Logger for reporting
* @return An array of articles not greater than the maximum length specified
* containing articles relating to the keywords
*
* @throws SQLException
* on database error
*/
public Article[] getRelatedArticles(int numberOfArticlesToReturn,
int maximumArticlesPerCategory,
int noOfDays,
boolean instantiateImages,
Category[] categories,
int orderBy,
boolean onlySVAVideoArticles,
int[] videoTypeIdArray,
boolean onlyLinkedObjectArticles,
DetailType detailType,
int detailId,
int[] detailTypeCategories,
boolean filterOutThisArticleFromResults,
Connection dbConnection,
Logger logger)
throws SQLException {
//
// Query to return articles which share any of the specified keywords.
//
// Articles are grouped together by category and the DECODE in the ordering
// is there to ensure that the World categories are listed first.
//
return this.getRelatedArticles(1,
numberOfArticlesToReturn,
maximumArticlesPerCategory,
noOfDays,
instantiateImages,
categories,
orderBy,
onlySVAVideoArticles,
videoTypeIdArray,
onlyLinkedObjectArticles,
detailType,
detailId,
detailTypeCategories,
filterOutThisArticleFromResults,
null, // Protect existing behaviour of not including syndication.
dbConnection,
logger);
}
/**
* Returns an array of <code>Articles</code> when given ranking and article
* category. It also takes in an integer defining how many articles to be
* returned by this method.<p>
* Articles in a return array will be fully populated.
* This method will only return ranked articles that belong to this site.
* @param category A <code>Category</code> for which the ranked articles
* are required. This parameter is ignored if the ranking
* required is for this.HOME_PAGE_RANKING rank.
* @param restrictNumberOfRowsReturned
* An integer representing how many articles should be
* returned by this method. <p>
* If this parameter is set to Article.ALL_ARTICLES then,
* depending on rank parameter this method will either <p>
* - return a list of all articles for the rank for the
* selected category, <p>
* - return a list of all articles ranked for the home
* page if rank is set to this.HOME_PAGE_RANKING.
* @param ownedAndUsedBy A <code>Site</code> for which a list of ranked
* articles is requested. This will be the same as the
* site that owns the articles (a site cannot get a
* ranked article for any other site).
* @param liveArticlesOnly
* A boolean indicating whether only live articles or all
* <code>Articles</code> are required. If this flag is set
* to <code>true</code> then only live articles will be
* searched for.
* @param videoInclusion Determines whether articles with videos should be
* returned. This has one of three values:
* 1) WITH_VIDEOS only articles with videos are
* returned
* 2) WITHOUT_VIDEOS only articles without videos are
* returned
* 3) ALL_ARTICLES all articles regardless of whether
* they have videos
* @param con A <code>Connection</code> to use in the method.
* @param logger A <code>Logger</code> to use in the method.
*
* @return An array of articles for the category passed to this method ranked
* by rank parameter used when calling this method.
*
* @exception SQLException if a database access error occurs
*/
public static Article[] getArticlesByRank(Category category,
int restrictNumberOfRowsReturned,
Site ownedAndUsedBy,
boolean liveArticlesOnly,
int videoInclusion,
Connection con,
Logger logger)
throws SQLException {
return getArticlesByRank(category,
restrictNumberOfRowsReturned,
1,
ownedAndUsedBy,
liveArticlesOnly,
null,
null,
videoInclusion,
con,
logger);
}
/**
* Returns an array of <code>Articles</code> when given ranking and article
* category. It also takes in an integer defining how many articles to be
* returned by this method.<p>
* Articles in a return array will be fully populated.
* This method will only return ranked articles that belong to this site.
* @param category A <code>Category</code> for which the ranked articles
* are required. This parameter is ignored if the ranking
* required is for this.HOME_PAGE_RANKING rank.
* @param restrictNumberOfRowsReturned
* An integer representing how many articles should be
* returned by this method. <p>
* If this parameter is set to Article.ALL_ARTICLES then,
* depending on rank parameter this method will either <p>
* - return a list of all articles for the rank for the
* selected category, <p>
* - return a list of all articles ranked for the home
* page if rank is set to this.HOME_PAGE_RANKING.
* @param ownedAndUsedBy A <code>Site</code> for which a list of ranked
* articles is requested. This will be the same as the
* site that owns the articles (a site cannot get a
* ranked article for any other site).
* @param liveArticlesOnly
* A boolean indicating whether only live articles or all
* <code>Articles</code> are required. If this flag is set
* to <code>true</code> then only live articles will be
* searched for.
* @param con A <code>Connection</code> to use in the method.
* @param logger A <code>Logger</code> to use in the method.
*
* @return An array of articles for the category passed to this method ranked
* by rank parameter used when calling this method.
*
* @exception SQLException if a database access error occurs
*/
public static Article[] getArticlesByRank(Category category,
int restrictNumberOfRowsReturned,
Site ownedAndUsedBy,
boolean liveArticlesOnly,
Connection con,
Logger logger)
throws SQLException {
return Article.getArticlesByRank(category, restrictNumberOfRowsReturned,
ownedAndUsedBy, liveArticlesOnly, Article.ALL_ARTICLES, con, logger);
}
/**
* Returns an array of <code>Articles</code> when given ranking and article
* category. It also takes in an integer defining how many articles to be
* returned by this method.<p>
* Articles in a return array will be fully populated.
* This method will only return ranked articles that belong to this site.
* @param category A <code>Category</code> for which the ranked articles
* are required. This parameter is ignored if the ranking
* required is for this.HOME_PAGE_RANKING rank.
* @param restrictNumberOfRowsReturned
* An integer representing how many articles should be
* returned by this method. <p>
* If this parameter is set to Article.ALL_ARTICLES then,
* depending on rank parameter this method will either <p>
* - return a list of all articles for the rank for the
* selected category, <p>
* - return a list of all articles ranked for the home
* page if rank is set to this.HOME_PAGE_RANKING.
* @param startFromPosition Allows the article index to be offset from the
* start. For example, if this is set to 2, the method
* will still return the same number of articles, but
* will skip over the article which would otherwise
* have been first.
* @param ownedAndUsedBy A <code>Site</code> for which a list of ranked
* articles is requested. This will be the same as the
* site that owns the articles (a site cannot get a
* ranked article for any other site).
* @param liveArticlesOnly
* A boolean indicating whether only live articles or all
* <code>Articles</code> are required. If this flag is set
* to <code>true</code> then only live articles will be
* searched for.
* @param java.sql.Date startDate
* @param java.sql.Date endDate
* @param videoInclusion Determines whether articles with videos should be
* returned. This has one of three values:
* 1) WITH_VIDEOS only articles with videos are
* returned
* 2) WITHOUT_VIDEOS only articles without videos are
* returned
* 3) ALL_ARTICLES all articles regardless of whether
* they have videos
* @param con A <code>Connection</code> to use in the method.
* @param logger A <code>Logger</code> to use in the method.
*
* @return An array of articles for the category passed to this method ranked
* by rank parameter used when calling this method.
*
* @exception SQLException if a database access error occurs
*/
public static Article[] getArticlesByRank(Category category,
int restrictNumberOfRowsReturned,
int startFromPosition,
Site ownedAndUsedBy,
boolean liveArticlesOnly,
java.sql.Date startDate,
java.sql.Date endDate,
int videoInclusion,
Connection con,
Logger logger)
throws SQLException {
return getArticlesByRank(category,
restrictNumberOfRowsReturned,
startFromPosition,
ownedAndUsedBy,
null, // Default behaviour is not to consider syndication
false, // Not syndicating, so interleaving is ignored
liveArticlesOnly,
false, //exclude home page articles.
startDate,
endDate,
videoInclusion,
con,
logger);
}
/**
* Returns an array of <code>Articles</code> when given ranking and article
* category. It also takes in an integer defining how many articles to be
* returned by this method.<p>
* Articles in a return array will be fully populated.
* This method will only return ranked articles that belong to this site.
* @param category A <code>Category</code> for which the ranked articles
* are required. This parameter is ignored if the ranking
* required is for this.HOME_PAGE_RANKING rank.
* @param restrictNumberOfRowsReturned
* An integer representing how many articles should be
* returned by this method. <p>
* If this parameter is set to Article.ALL_ARTICLES then,
* depending on rank parameter this method will either <p>
* - return a list of all articles for the rank for the
* selected category, <p>
* - return a list of all articles ranked for the home
* page if rank is set to this.HOME_PAGE_RANKING.
* @param startFromPosition Allows the article index to be offset from the
* start. For example, if this is set to 2, the method
* will still return the same number of articles, but
* will skip over the article which would otherwise
* have been first.
* @param ownedAndUsedBy A <code>Site</code> for which a list of ranked
* articles is requested. This will be the same as the
* site that owns the articles (a site cannot get a
* ranked article for any other site).
* @param liveArticlesOnly
* A boolean indicating whether only live articles or all
* <code>Articles</code> are required. If this flag is set
* to <code>true</code> then only live articles will be
* searched for.
* @param java.sql.Date startDate
* @param java.sql.Date endDate
* @param videoInclusion Determines whether articles with videos should be
* returned. This has one of three values:
* 1) WITH_VIDEOS only articles with videos are
* returned
* 2) WITHOUT_VIDEOS only articles without videos are
* returned
* 3) ALL_ARTICLES all articles regardless of whether
* they have videos
* @param con A <code>Connection</code> to use in the method.
* @param logger A <code>Logger</code> to use in the method.
*
* @return An array of articles for the category passed to this method ranked
* by rank parameter used when calling this method.
*
* @exception SQLException if a database access error occurs
*
* @deprecated Use {@link Article#getArticlesByRank(Category, int, int, Site, boolean, java.sql.Date, java.sql.Date, int, Connection, Logger)}
* instead.
*/
@Deprecated
public static Article[] getArticlesByRank(Category category,
int restrictNumberOfRowsReturned,
int startFromPosition,
Site ownedAndUsedBy,
boolean liveArticlesOnly,
java.sql.Date startDate,
java.sql.Date endDate,
Connection con,
Logger logger)
throws SQLException {
return Article.getArticlesByRank(category, restrictNumberOfRowsReturned,
startFromPosition, ownedAndUsedBy, liveArticlesOnly, startDate,
endDate, Article.ALL_ARTICLES, con, logger);
}
/**
* Returns an array of <code>Articles</code> when given ranking and article
* category. It also takes in an integer defining how many articles to be
* returned by this method.<p>
* Articles in a return array will be fully populated.
* This method will only return ranked articles that belong to this site.
* @param category A <code>Category</code> for which the ranked articles
* are required. This parameter is ignored if the ranking
* required is for this.HOME_PAGE_RANKING rank.
* @param restrictNumberOfRowsReturned
* An integer representing how many articles should be
* returned by this method. <p>
* If this parameter is set to Article.ALL_ARTICLES then,
* depending on rank parameter this method will either <p>
* - return a list of all articles for the rank for the
* selected category, <p>
* - return a list of all articles ranked for the home
* page if rank is set to this.HOME_PAGE_RANKING.
* @param startFromPosition Allows the article index to be offset from the
* start. For example, if this is set to 2, the method
* will still return the same number of articles, but
* will skip over the article which would otherwise
* have been first.
* @param ownedAndUsedBy A <code>Site</code> for which a list of ranked
* articles is requested. This will be the same as the
* site that owns the articles (a site cannot get a
* ranked article for any other site).
* @param syndicationPartners If this is non-null and the category is a
* syndication category, then the ranking will be applied
* to both local articles in that category as well as
* syndicated articles from the syndication partners. If
* this is {@code null} or the category is not a syndication
* category, then only articles for the site specified in
* {@code ownedAndUsedBy} will be considered.
* @param interleaveSyndicatedArticles
* @param liveArticlesOnly
* A boolean indicating whether only live articles or all
* <code>Articles</code> are required. If this flag is set
* to <code>true</code> then only live articles will be
* searched for.
* @param excludeHomePageArticles
* A boolean indicating whether, when to include articles
* with the home page flag set to true. This parameter will
* be ignored if the ranking required is for
* this.HOME_PAGE_RANKING rank.
* @param java.sql.Date startDate
* @param java.sql.Date endDate
* @param videoInclusion Determines whether articles with videos should be
* returned. This has one of three values:
* 1) WITH_VIDEOS only articles with videos are
* returned
* 2) WITHOUT_VIDEOS only articles without videos are
* returned
* 3) ALL_ARTICLES all articles regardless of whether
* they have videos
* @param con A <code>Connection</code> to use in the method.
* @param logger A <code>Logger</code> to use in the method.
*
* @return An array of articles for the category passed to this method ranked
* by rank parameter used when calling this method.
*
* @exception SQLException if a database access error occurs
*/
public static Article[] getArticlesByRank(final Category category,
final int restrictNumberOfRowsReturned,
final int startFromPosition,
final Site ownedAndUsedBy,
final Collection<Site> syndicationPartners,
final boolean interleaveSyndicatedArticles,
final boolean liveArticlesOnly,
final boolean excludeHomePageArticles,
final java.sql.Date startDate,
final java.sql.Date endDate,
final int videoInclusion,
final Connection con,
final Logger logger)
throws SQLException {
String rankCode;
if (null == category) {
rankCode = Article.HOME_CATEGORY_RANK_TYPE;
} else {
rankCode = Article.CATEGORY_RANK_TYPE;
}
return Article.getArticlesByRank(rankCode,
category,
restrictNumberOfRowsReturned,
startFromPosition,
ownedAndUsedBy,
syndicationPartners,
interleaveSyndicatedArticles,
liveArticlesOnly,
excludeHomePageArticles,
startDate,
endDate,
videoInclusion,
con,
logger);
}
/**
* Returns an array of <code>Articles</code> when given ranking and article
* category. It also takes in an integer defining how many articles to be
* returned by this method.<p>
* Articles in a return array will be fully populated.
* This method will only return ranked articles that belong to this site.
* @param category A <code>Category</code> for which the ranked articles
* are required. This parameter is ignored if the ranking
* required is for this.HOME_PAGE_RANKING rank.
* @param restrictNumberOfRowsReturned
* An integer representing how many articles should be
* returned by this method. <p>
* If this parameter is set to Article.ALL_ARTICLES then,
* depending on rank parameter this method will either <p>
* - return a list of all articles for the rank for the
* selected category, <p>
* - return a list of all articles ranked for the home
* page if rank is set to this.HOME_PAGE_RANKING.
* @param startFromPosition Allows the article index to be offset from the
* start. For example, if this is set to 2, the method
* will still return the same number of articles, but
* will skip over the article which would otherwise
* have been first.
* @param ownedAndUsedBy A <code>Site</code> for which a list of ranked
* articles is requested. This will be the same as the
* site that owns the articles (a site cannot get a
* ranked article for any other site).
* @param liveArticlesOnly
* A boolean indicating whether only live articles or all
* <code>Articles</code> are required. If this flag is set
* to <code>true</code> then only live articles will be
* searched for.
* @param excludeHomePageArticles
* A boolean indicating whether, when to include articles
* with the home page flag set to true. This parameter will
* be ignored if the ranking required is for
* this.HOME_PAGE_RANKING rank.
* @param java.sql.Date startDate
* @param java.sql.Date endDate
* @param videoInclusion Determines whether articles with videos should be
* returned. This has one of three values:
* 1) WITH_VIDEOS only articles with videos are
* returned
* 2) WITHOUT_VIDEOS only articles without videos are
* returned
* 3) ALL_ARTICLES all articles regardless of whether
* they have videos
* @param con A <code>Connection</code> to use in the method.
* @param logger A <code>Logger</code> to use in the method.
*
* @return An array of articles for the category passed to this method ranked
* by rank parameter used when calling this method.
*
* @exception SQLException if a database access error occurs
*
* @deprecated Use {@link Article#getArticlesByRank(Category, int, int, Site, boolean, boolean, java.sql.Date, java.sql.Date, int, Connection, Logger)}
* in preference.
*/
@Deprecated
public static Article[] getArticlesByRank(Category category,
int restrictNumberOfRowsReturned,
int startFromPosition,
Site ownedAndUsedBy,
boolean liveArticlesOnly,
boolean excludeHomePageArticles,
java.sql.Date startDate,
java.sql.Date endDate,
Connection con,
Logger logger)
throws SQLException {
return Article.getArticlesByRank(category, restrictNumberOfRowsReturned,
startFromPosition, ownedAndUsedBy, liveArticlesOnly, startDate, endDate,
Article.ALL_ARTICLES, con, logger);
}
/**
* Returns an array of <code>Articles</code> when given ranking and article
* category. It also takes in an integer defining how many articles to be
* returned by this method.<p>
* Articles in a return array will be fully populated.
* This method will only return ranked articles that belong to this site.
*
* NOTE: The sql in this article must match that in getArticlesByRankCount
*
* @param rank An int defining what ranking to be applied when
* retrieving data. These can be one of the following
* values:
* - this.HOME_PAGE_RANKING
* - this.ARTICLE_SHARING_RANKING
* - this.CATEGORY_RANKING
* @param category A <code>Category</code> for which the ranked articles
* are required. This parameter is ignored if the ranking
* required is for this.HOME_PAGE_RANKING rank.
* @param restrictNumberOfRowsReturned
* An integer representing how many articles should be
* returned by this method. <p>
* If this parameter is set to Article.ONLY_RANKED_ARTICLES then,
* only articles with a rank will be returned.
* @param startFromPosition Allows the article index to be offset from the
* start. For example, if this is set to 2, the method
* will still return the same number of articles, but
* will skip over the article which would otherwise
* have been first.
* @param ownedAndUsedBy A <code>Site</code> for which a list of ranked
* articles is requested. This will be the same as the
* site that owns the articles (a site cannot get a
* ranked article for any other site).
* @param syndicationPartners TODO Document me
* @param interleaveSyndicatedArticles TODO Document me
* @param liveArticlesOnly
* A boolean indicating whether only live articles or all
* <code>Articles</code> are required. If this flag is set
* to <code>true</code> then only live articles will be
* searched for.
* @param excludeHomePageArticles
* A boolean indicating whether, when to include articles
* with the home page flag set to true. This parameter will
* be ignored if the ranking required is for
* this.HOME_PAGE_RANKING rank.
* @param java.sql.Date startDate
* @param java.sql.Date endDate
* @param videoInclusion Determines whether articles with videos should be
* returned. This has one of three values:
* 1) WITH_VIDEOS only articles with videos are
* returned
* 2) WITHOUT_VIDEOS only articles without videos are
* returned
* 3) ALL_ARTICLES all articles regardless of whether
* they have videos
* @param con A <code>Connection</code> to use in the method.
* @param logger A <code>Logger</code> to use in the method.
*
* @return An array of articles for the category passed to this method ranked
* by rank parameter used when calling this method.
*
* @exception SQLException if a database access error occurs
*/
private static Article[] getArticlesByRank(final String rankCode,
final Category category,
int restrictNumberOfRowsReturned,
final int startFromPosition,
final Site ownedAndUsedBy,
final Collection<Site> syndicationPartners,
final boolean interleaveSyndicatedArticles,
final boolean liveArticlesOnly,
final boolean excludeHomePageArticles,
final java.sql.Date startDate,
final java.sql.Date endDate,
final int videoInclusion,
final Connection con,
final Logger logger)
throws SQLException {
logger.debug(
"Trying to get an array of articles by rank by category " +
(null != category ? category.getCategoryId() : -1)
);
final boolean isSyndicated = (syndicationPartners != null) &&
!syndicationPartners.isEmpty() &&
(category != null) &&
category.isUsedInSyndication();
//
// Return value
//
Article[] articlesToReturn = null;
//
// This query is a horrible beast. Horrible, horrible beast. I'm adding to
// this beast. I feel dirty. At some stage it really needs some serious
// loving. It's just horrible.
//
StringBuilder sqlCmd = new StringBuilder("SELECT * FROM ( ");
sqlCmd.append("SELECT rownum as rnum, ").append(SQL_ARTICLE_COLUMNS).append(" FROM ( ");
sqlCmd.append("SELECT ").append(SQL_ARTICLE_COLUMNS);
sqlCmd.append("FROM editorial_articles a, ( ");
if(isSyndicated && interleaveSyndicatedArticles) {
sqlCmd.append("SELECT artl_id, (seq_no * 10) as seq_no ");
} else {
sqlCmd.append("SELECT artl_id, seq_no ");
}
sqlCmd.append("FROM ranks ");
sqlCmd.append("WHERE rnkt_cd = ? AND orgn_id = ? ");
if(category != null) {
sqlCmd.append("AND catg_id = ? ");
}
if(isSyndicated) {
sqlCmd.append("UNION ");
if(interleaveSyndicatedArticles) {
sqlCmd.append("SELECT artl_id, (seq_no * 10) + 1 as seq_no ");
} else {
sqlCmd.append("SELECT artl_id, (seq_no + 10) as seq_no ");
}
sqlCmd.append("FROM ranks ");
sqlCmd.append("WHERE rnkt_cd = ? AND orgn_id in (");
sqlCmd.append(ConnectionPool.getInList(syndicationPartners));
sqlCmd.append(") ");
if(category != null) {
sqlCmd.append("AND catg_id = ? ");
}
}
if(restrictNumberOfRowsReturned != ONLY_RANKED_ARTICLES) {
sqlCmd.append("UNION ");
sqlCmd.append("SELECT artl_id, 10000 as seq_no FROM ( ");
sqlCmd.append("SELECT artl_id ");
sqlCmd.append("FROM editorial_articles ");
if(isSyndicated) {
sqlCmd.append("WHERE (orgn_club_id IN (?, ");
sqlCmd.append(ConnectionPool.getInList(syndicationPartners));
sqlCmd.append(") OR general_flg = 'Y') ");
} else {
sqlCmd.append("WHERE (orgn_club_id = ? OR general_flg = 'Y') ");
}
if(category != null) {
sqlCmd.append("AND catg_id = ? ");
} else {
sqlCmd.append("AND hmpg_flg = 'Y' ");
}
if(liveArticlesOnly) {
sqlCmd.append("AND SYSDATE >= article_date ");
sqlCmd.append("AND SYSDATE >= NVL(site_posted_date, TO_DATE('01-Jan-3001', 'DD-Mon-YYYY')) ");
sqlCmd.append("AND SYSDATE < NVL(site_removed_date, TO_DATE('01-Jan-3001', 'DD-Mon-YYYY')) ");
}
sqlCmd.append(Article.getVideoInclusionSQL(videoInclusion));
if((startDate != null) && (endDate != null)) {
sqlCmd.append(" AND article_date >= ? ");
sqlCmd.append(" AND article_date < ? ");
}
sqlCmd.append("AND artl_id NOT IN ( ");
sqlCmd.append("SELECT artl_id ");
sqlCmd.append("FROM ranks ");
sqlCmd.append("WHERE rnkt_cd = ? ");
if(isSyndicated) {
sqlCmd.append("AND orgn_id IN (?, ");
sqlCmd.append(ConnectionPool.getInList(syndicationPartners));
sqlCmd.append(") ");
} else {
sqlCmd.append("AND orgn_id = ? ");
}
if(category != null) {
sqlCmd.append("AND catg_id = ? ");
}
sqlCmd.append(") ORDER BY article_date DESC, headline ASC ");
sqlCmd.append(") ");
}
if(restrictNumberOfRowsReturned != ONLY_RANKED_ARTICLES) {
sqlCmd.append("WHERE ROWNUM <= ?");
}
sqlCmd.append(") r ");
sqlCmd.append("WHERE a.artl_id = r.artl_id ");
if(liveArticlesOnly) {
sqlCmd.append(LIVE_ARTICLES_SQL);
}
if((category != null) && excludeHomePageArticles) {
sqlCmd.append(" AND hmpg_flg = 'N' ");
}
sqlCmd.append(Article.getVideoInclusionSQL(videoInclusion, "a."));
sqlCmd.append("ORDER BY seq_no ASC, article_date DESC, headline ASC ");
sqlCmd.append(") a ");
sqlCmd.append(") ");
if(restrictNumberOfRowsReturned != ONLY_RANKED_ARTICLES) {
sqlCmd.append("WHERE rnum <= ? AND rnum >= ?");
}
if((startDate != null) && (endDate != null)) {
sqlCmd.append(" AND article_date >= ? AND article_date < ? ");
}
PreparedStatement preparedStatement = ConnectionPool.prepareStatement(sqlCmd.toString(), con,logger);
try {
int param = 0;
//
// Set parameters to be passed to the above SQL query, depending on
// what rank was passed to this method and if all articles where
// requested.
//
preparedStatement.setString(++param, rankCode);
preparedStatement.setInt(++param, ownedAndUsedBy.getId());
if (null != category) {
preparedStatement.setInt(++param, category.getCategoryId());
}
if(isSyndicated) {
preparedStatement.setString(++param, rankCode);
for(Site syndPartner: syndicationPartners) {
preparedStatement.setInt(++param, syndPartner.getId());
}
if (null != category) {
preparedStatement.setInt(++param, category.getCategoryId());
}
}
if (Article.ONLY_RANKED_ARTICLES != restrictNumberOfRowsReturned) {
preparedStatement.setInt(++param, ownedAndUsedBy.getId());
if(isSyndicated) {
for(Site syndPartner: syndicationPartners) {
preparedStatement.setInt(++param, syndPartner.getId());
}
}
if (null != category) {
preparedStatement.setInt(++param, category.getCategoryId());
}
if (startDate != null && endDate != null) {
preparedStatement.setDate(++param,startDate);
preparedStatement.setDate(++param,endDate);
}
preparedStatement.setString(++param, rankCode);
preparedStatement.setInt(++param, ownedAndUsedBy.getId());
if(isSyndicated) {
for(Site syndPartner: syndicationPartners) {
preparedStatement.setInt(++param, syndPartner.getId());
}
}
if (null != category) {
preparedStatement.setInt(++param, category.getCategoryId());
}
//
// Take the start from position into account. If this is greater than
// 1 it means rows will be returned in the inner query some of which
// will be discarded by the last rnum clause.
//
restrictNumberOfRowsReturned += (startFromPosition - 1);
preparedStatement.setInt(++param, restrictNumberOfRowsReturned);
preparedStatement.setInt(++param, restrictNumberOfRowsReturned);
preparedStatement.setInt(++param, startFromPosition);
}
if (startDate != null && endDate != null) {
preparedStatement.setDate(++param,startDate);
preparedStatement.setDate(++param,endDate);
}
articlesToReturn = Article.getArticles(preparedStatement,
true,
ownedAndUsedBy,
false,
con,
logger);
} finally {
preparedStatement.close();
}
return articlesToReturn;
}
/**
* Gets all of the syndicated articles in the database depending on the
* supplied parameters. Only ever gets articles that have been launched but
* have not yet been expired. Doesn't bother retrieving the images.
*
* @param competitionIds this will then include all clubs that are currently
* taking part in the these competitions.
* @param connection
* @param logger
* @return array of articles
* @throws SQLException
*/
public static Article[] getSyndicatedArticlesOnePerClub(List competitionIds,
Connection connection,
Logger logger)
throws SQLException {
Article[] articlesToReturn = null;
String compList = "";
String compYear = null;
if(competitionIds != null) {
Iterator it = competitionIds.iterator();
while (it.hasNext()) {
compList += it.next() + (it.hasNext() ? ", " : "");
}
}
//
// get the current season by supplying the premiership and today's date
//
compYear = Competition.getSeasonCode(1, new Date(), connection, logger);
//
// create the sql tring to include in query
//
if (compList.length() > 0) {
PreparedStatement preparedStatement = ConnectionPool.prepareStatement(
"SELECT " + Article.SQL_ARTICLE_COLUMNS +
"FROM editorial_articles a, categories c " +
"WHERE a.catg_id = c.catg_id " +
"AND a.artl_id IN ( " +
"SELECT MAX(artl_id) AS max_artl_id FROM ( " +
"SELECT a.artl_id, a.orgn_club_id, a.article_date " +
"FROM editorial_articles a, categories c " +
"WHERE a.article_date > SYSDATE - 90 " +
"AND a.syndicated_flg = 'Y' " +
"AND a.orgn_club_id in (" +
"SELECT t.orgn_id " +
"FROM competition_entries c, teams t " +
"WHERE c.cmpt_id in (" + compList + ") " +
"AND c.sesn_cd = " + compYear + " " +
"AND t.team_id = c.team_id" +
") " +
"AND a.catg_id = c.catg_id " +
"AND c.football_league_synd_flg = 'Y'" +
Article.LIVE_ARTICLES_SQL +
") " +
"GROUP BY orgn_club_id " +
") " +
"ORDER BY a.article_date DESC",
connection,
logger);
try {
//
// only ever get live articles and dont bother with images
//
articlesToReturn = Article.getArticles(preparedStatement,
false,
null,
false,
connection,
logger);
} finally {
preparedStatement.close();
}
}
return articlesToReturn;
}
/**
* Gets all of the syndicated articles in the database depending on the
* supplied parameters. Only ever gets articles that have been launched but
* have not yet been expired. Doesn't bother retrieving the images.
*
* @param competitionIds this will then include all clubs that are currently
* taking part in the these competitions.
* @param connection
* @param logger
* @return array of articles
* @throws SQLException
*/
public static Article[] getSyndicatedArticles(List competitionIds,
int numberOfArticles,
Connection connection,
Logger logger)
throws SQLException {
Article[] articlesToReturn = null;
String compList = "";
String compYear = null;
if(competitionIds != null) {
Iterator it = competitionIds.iterator();
while (it.hasNext()) {
compList += it.next() + (it.hasNext() ? ", " : "");
}
}
//
// get the current season by supplying the premiership and today's date
//
compYear = Competition.getSeasonCode(1, new Date(), connection, logger);
//
// create the sql tring to include in query
//
if (compList.length() > 0) {
PreparedStatement preparedStatement = ConnectionPool.prepareStatement(
"SELECT * FROM (" +
"SELECT ROWNUM AS num, x.* FROM ( " +
"SELECT " + Article.SQL_ARTICLE_COLUMNS +
"FROM editorial_articles a, categories c " +
"WHERE a.article_date > SYSDATE - 14 " +
"AND a.syndicated_flg = 'Y' " +
(compList != null ?
"AND a.orgn_club_id in (" +
"SELECT t.orgn_id " +
"FROM competition_entries c, teams t " +
"WHERE c.cmpt_id in (" + compList + ") " +
"AND c.sesn_cd = " + compYear + " " +
"AND t.team_id = c.team_id) " : ""
) +
"AND a.catg_id = c.catg_id " +
"AND c.football_league_synd_flg = 'Y'" +
Article.LIVE_ARTICLES_SQL +
"ORDER BY a.article_date DESC" +
") x " +
") " +
"WHERE num <= " + numberOfArticles,
connection,
logger);
try {
//
// only ever get live articles and dont bother with images
//
articlesToReturn = Article.getArticles(preparedStatement,
false,
null,
false,
connection,
logger);
} finally {
preparedStatement.close();
}
}
return articlesToReturn;
}
/**
* Gets all of the syndicated articles from the supplied list of clubs
* depending on the supplied parameters. Only ever gets articles that have
* been launched, haven't expired and doesn't bother retrieving images.
*
* @param sites an array of sites whose articles we want to get
* @param numberOfArticles the number of articles we want to retrieve
* @param connection
* @param logger
* @return array of articles
* @throws SQLException
*/
public static Article[] getSyndicatedArticles(Site[] sites,
int numberOfArticles,
Connection connection,
Logger logger)
throws SQLException {
Article[] articlesToReturn = null;
int[] siteIds = new int[sites.length];
if (sites != null) {
Integer index = 0;
for (Site site : sites) {
siteIds[index] = site.getId();
index++;
}
}
if (siteIds.length > 0) {
PreparedStatement preparedStatement = ConnectionPool.prepareStatement(
"SELECT * FROM (" +
"SELECT " + Article.SQL_ARTICLE_COLUMNS +
"FROM editorial_articles a " +
"WHERE a.article_date > SYSDATE - 14 " +
"AND a.syndicated_flg = 'Y' " +
"AND a.orgn_club_id in (" + ConnectionPool.getInList(siteIds) + ") " +
Article.LIVE_ARTICLES_SQL +
"ORDER BY a.article_date DESC" +
") " +
"WHERE ROWNUM <= ?",
connection,
logger);
try {
int lastInListParamNum = ConnectionPool.setInListParameters(preparedStatement, 0, siteIds);
preparedStatement.setInt(++lastInListParamNum, numberOfArticles);
//
// only ever get live articles and dont bother with images
//
articlesToReturn = Article.getArticles(preparedStatement,
false,
null,
false,
connection,
logger);
} finally {
preparedStatement.close();
}
}
return articlesToReturn;
}
/**
* Looks in the database for a syndicated article with the specified
* article id.
*
* @param articleId The requested article identifier.
* @param liveArticlesOnly
* A boolean indicating whether only live articles are
* required. If this flag is set to <code>true</code>
* then only live articles will be searched for.
* @param instantiateImages Specifies whether images are instantiated
* @param con A <code>Connection</code> to use in the method.
* @param logger A <code>Logger</code> to use in the method.
*
* @return The <code>Article</code> requested, or null if not found.
*
* @exception SQLException if a database access error occurs
*/
public static Article getSyndicatedArticle(int articleId,
boolean liveArticlesOnly,
boolean instantiateImages,
Connection con,
Logger logger)
throws SQLException {
PreparedStatement preparedStatement = ConnectionPool.prepareStatement(
" SELECT " + Article.SQL_ARTICLE_COLUMNS +
" FROM editorial_articles a " +
" WHERE a.artl_id = " + articleId +
" AND syndicated_flg = 'Y' " +
(liveArticlesOnly ? Article.LIVE_ARTICLES_SQL : ""),
con,
logger
);
Article article = null;
Article[] articles =
Article.getArticles(preparedStatement,
instantiateImages,
null,
false, //retrieve linked objects
con,
logger);
if (null != articles && articles.length > 0) {
article = articles[0];
}
return article;
}
/**
* Gets all of the syndicated articles from the supplied list of clubs
* between the two specified dates.
*
* @param sites an array of sites whose articles we want to get
* @param numberOfArticles the number of articles we want to retrieve
* @param startDate the start date
* @param endDate the end date
* @param connection
* @param logger
* @return array of articles
* @throws SQLException
*/
public static Article[] getSyndicatedArticles(Site[] sites,
int numberOfArticles,
Date startDate,
Date endDate,
Connection connection,
Logger logger)
throws SQLException {
Article[] articlesToReturn = null;
String siteList = "";
if (sites != null) {
for(int ii = 0; ii < sites.length; ii++) {
siteList += (ii > 0 ? ", " : "") + sites[ii].getId();
}
}
//
// create the sql tring to include in query
//
if (siteList.length() > 0) {
PreparedStatement preparedStatement = ConnectionPool.prepareStatement(
"SELECT * FROM (" +
"SELECT " + Article.SQL_ARTICLE_COLUMNS +
"FROM editorial_articles a " +
"WHERE a.syndicated_flg = 'Y' " +
(null != endDate ? "AND a.article_date <= ? " : "") +
(null != startDate ? "AND a.article_date >= ? " : "") +
"AND a.orgn_club_id in (" + siteList + ") " +
Article.LIVE_ARTICLES_SQL +
"ORDER BY a.article_date DESC" +
") " +
"WHERE ROWNUM <= " + numberOfArticles,
connection,
logger);
int param = 1;
if (null != endDate) {
preparedStatement.setDate(param++, new java.sql.Date(endDate.getTime()));
}
if (null != startDate) {
preparedStatement.setDate(param++, new java.sql.Date(startDate.getTime()));
}
try {
//
// only ever get live articles and dont bother with images
//
articlesToReturn = Article.getArticles(preparedStatement,
false,
null,
false,
connection,
logger);
} finally {
preparedStatement.close();
}
}
return articlesToReturn;
}
/**
* Gets all of the syndicated articles in the database depending on the
* supplied parameters. Only ever gets articles that have been launched but
* have not yet been expired. Doesn't bother retrieving the images.
*
* @param competitionIds this will then include all clubs that are currently
* taking part in the these competitions.
* @param startDate the start date
* @param endDate the end date
* @param connection
* @param logger
* @return array of articles
* @throws SQLException
*/
public static Article[] getSyndicatedArticles(List competitionIds,
int numberOfArticles,
Date startDate,
Date endDate,
Connection connection,
Logger logger)
throws SQLException {
Article[] articlesToReturn = null;
String compList = "";
String compYear = null;
if(competitionIds != null) {
Iterator it = competitionIds.iterator();
while (it.hasNext()) {
compList += it.next() + (it.hasNext() ? ", " : "");
}
}
//
// get the current season by supplying the premiership and today's date
//
compYear = Competition.getSeasonCode(1, new Date(), connection, logger);
//
// create the sql tring to include in query
//
if (compList.length() > 0) {
PreparedStatement preparedStatement = ConnectionPool.prepareStatement(
"SELECT * FROM (" +
"SELECT ROWNUM AS num, x.* FROM ( " +
"SELECT " + Article.SQL_ARTICLE_COLUMNS +
"FROM editorial_articles a, categories c " +
"WHERE a.syndicated_flg = 'Y' " +
(null != endDate ? "AND a.article_date <= ? " : "") +
(null != startDate ? "AND a.article_date >= ? " : "") +
(compList != null ?
"AND a.orgn_club_id in (" +
"SELECT t.orgn_id " +
"FROM competition_entries c, teams t " +
"WHERE c.cmpt_id in (" + compList + ") " +
"AND c.sesn_cd = " + compYear + " " +
"AND t.team_id = c.team_id) " : ""
) +
"AND a.catg_id = c.catg_id " +
"AND c.football_league_synd_flg = 'Y'" +
Article.LIVE_ARTICLES_SQL +
"ORDER BY a.article_date DESC" +
") x " +
") " +
"WHERE num <= " + numberOfArticles,
connection,
logger);
int param = 1;
if (null != endDate) {
preparedStatement.setDate(param++, new java.sql.Date(endDate.getTime()));
}
if (null != startDate) {
preparedStatement.setDate(param++, new java.sql.Date(startDate.getTime()));
}
try {
//
// only ever get live articles and dont bother with images
//
articlesToReturn = Article.getArticles(preparedStatement,
false,
null,
false,
connection,
logger);
} finally {
preparedStatement.close();
}
}
return articlesToReturn;
}
/**
* Gets the given number of articles that are to be displayed on the
* organisations mobile site that are no older than 30 days.
*
* @param site the site whose articles we want to get
* @param numberOfArticles the number of articles we want to retrieve
* @param connection
* @param logger
* @return array of articles
* @throws SQLException
*/
public static Article[] getMobileSiteArticles(Site site,
int numberOfArticles,
Connection connection,
Logger logger)
throws SQLException {
Article[] articlesToReturn = null;
//
// create the sql tring to include in query
//
if (site != null) {
PreparedStatement preparedStatement = ConnectionPool.prepareStatement(
"SELECT * FROM (" +
"SELECT " + Article.SQL_ARTICLE_COLUMNS +
"FROM editorial_articles a " +
"WHERE a.article_date > SYSDATE - 30 " +
"AND a.mobile_site_flg = 'Y' " +
"AND a.orgn_club_id = ? " +
Article.LIVE_ARTICLES_SQL +
"ORDER BY a.article_date DESC" +
") " +
"WHERE ROWNUM <= " + numberOfArticles,
connection,
logger);
preparedStatement.setInt(1, site.getId());
try {
//
// only ever get live articles and dont bother with images
//
articlesToReturn = Article.getArticles(preparedStatement,
false,
null,
false,
connection,
logger);
} finally {
preparedStatement.close();
}
}
return articlesToReturn;
}
/**
* Gets the given number of articles that are to be displayed on the
* organisations mobile site that are no older than 30 days.
*
* @param site the site whose articles we want to get
* @param numberOfArticles the number of articles we want to retrieve
* @param connection
* @param logger
* @return array of articles
* @throws SQLException
*/
public static Article[] getMobileSiteArticles(Site site,
String keyword,
int numberOfArticles,
Connection connection,
Logger logger)
throws SQLException {
Article[] articlesToReturn = null;
//
// create the sql tring to include in query
//
if (site != null) {
PreparedStatement preparedStatement = ConnectionPool.prepareStatement(
"SELECT * FROM (" +
"SELECT " + Article.SQL_ARTICLE_COLUMNS +
"FROM editorial_articles a, article_keywords k " +
"WHERE a.article_date > SYSDATE - 30 " +
"AND a.mobile_site_flg = 'Y' " +
"AND a.orgn_club_id = ? " +
"AND a.artl_id = k.artl_id " +
"AND k.orgn_id = ? " +
"AND k.keyword = ? " +
Article.LIVE_ARTICLES_SQL +
"ORDER BY a.article_date DESC" +
") " +
"WHERE ROWNUM <= " + numberOfArticles,
connection,
logger);
preparedStatement.setInt(1, site.getId());
preparedStatement.setInt(2, site.getId());
preparedStatement.setString(3, keyword);
try {
//
// only ever get live articles and dont bother with images
//
articlesToReturn = Article.getArticles(preparedStatement,
false,
null,
false,
connection,
logger);
} finally {
preparedStatement.close();
}
}
return articlesToReturn;
}
/**
* Gets the given number of articles that are to be displayed on the
* organisations podcast feed that are no older than 30 days.
*
* @param site the site whose articles we want to get
* @param numberOfArticles the number of articles we want to retrieve
* @param connection
* @param logger
* @return array of articles
* @throws SQLException
*/
public static Article[] getPodcastArticles(Site site,
int numberOfArticles,
Connection connection,
Logger logger) throws SQLException {
Article[] articlesToReturn = null;
//
// create the sql tring to include in query
//
if (site != null) {
PreparedStatement preparedStatement = ConnectionPool.prepareStatement(
"SELECT * FROM (" +
"SELECT " + Article.SQL_ARTICLE_COLUMNS +
"FROM editorial_articles a " +
"WHERE a.article_date > SYSDATE - 30 " +
"AND a.podcasting_flg = 'Y' " +
"AND a.orgn_club_id = ? " +
Article.LIVE_ARTICLES_SQL +
"ORDER BY a.article_date DESC" +
") " +
"WHERE ROWNUM <= " + numberOfArticles,
connection,
logger);
preparedStatement.setInt(1, site.getId());
try {
//
// only ever get live articles and dont bother with images
//
articlesToReturn = Article.getArticles(preparedStatement,
false,
null,
false,
connection,
logger);
} finally {
preparedStatement.close();
}
}
return articlesToReturn;
}
/**
* Returns an array with all the live article instances that are linked to
* the specified sva video id.
*
* @param videoId The video id to which the requested articles must be linked.
* @param numberOfArticles The maximum number of articles to return.
* @param site the site to which the articles to return must belong.
* @param connection Connection to the database.
* @param logger Access to the logs file.
*
* @return An array with all the live article instances that are linked to
* the specified video id.
*
* @throws SQLException
*/
public static Article[] getArticlesLinkedToVideoId(int videoId,
int numberOfArticles,
Site site,
Connection connection,
Logger logger) throws SQLException {
Article[] articlesToReturn = null;
if (site != null) {
PreparedStatement preparedStatement = ConnectionPool.prepareStatement(
" SELECT * FROM ( " +
" SELECT " + Article.SQL_ARTICLE_COLUMNS +
" FROM editorial_articles a " +
" WHERE a.orgn_club_id = ? " +
Article.LIVE_ARTICLES_SQL +
" AND a.artl_id in ( " +
" SELECT article_id FROM article_videos WHERE video_id = ? " +
" ) " +
" ORDER BY a.article_date DESC " +
" ) " +
" WHERE ROWNUM <= " + numberOfArticles,
connection,
logger);
preparedStatement.setInt(1, site.getId());
preparedStatement.setInt(2, videoId);
try {
//
// only ever get live articles and dont bother with images
//
articlesToReturn = Article.getArticles(
preparedStatement,
false, // don't bother instantiating images
site,
false, // no need for rankings
connection,
logger
);
} finally {
if (preparedStatement != null) {
preparedStatement.close();
}
}
}
return articlesToReturn;
}
/**
* Save the google processed date against the article.
*
* @param date the processed date
* @param connection
* @param logger
* @throws SQLException
*/
public void saveGoogleVideoProcessedDate(Date date,
Connection connection,
Logger logger) throws SQLException {
PreparedStatement preparedStatement = ConnectionPool.prepareStatement(
"UPDATE editorial_articles SET google_video_date = ? WHERE artl_id = ?",
connection,
logger
);
ConnectionPool.setDateAndTime(preparedStatement, 1, date);
preparedStatement.setInt(2, this.articleId);
try {
ConnectionPool.executeUpdate(preparedStatement, connection, logger);
} finally {
preparedStatement.close();
}
}
/**
* Gets the given number of articles that are to be syndicated to google video
* and are also no longer than 30 days old.
* @param connection
* @param logger
* @return array of articles
* @throws SQLException
*/
public static Article[] getGoogleVideoArticles(Date date,
Connection connection,
Logger logger) throws SQLException {
Article[] articlesToReturn = null;
PreparedStatement preparedStatement = ConnectionPool.prepareStatement(
"SELECT * FROM (" +
"SELECT " + Article.SQL_ARTICLE_COLUMNS +
"FROM editorial_articles a " +
"WHERE a.google_video_flg = 'Y' " +
"AND a.google_video_date = ? " +
Article.LIVE_ARTICLES_SQL +
"ORDER BY a.article_date DESC" +
") ",
connection,
logger);
ConnectionPool.setDateAndTime(preparedStatement, 1, date);
try {
//
// only ever get live articles and dont bother with images
//
articlesToReturn = Article.getArticles(preparedStatement,
false,
null,
false,
connection,
logger);
} finally {
preparedStatement.close();
}
return articlesToReturn;
}
/**
* Gets the given number of articles that are to be syndicated to google video
* and are also no longer than 30 days old.
* @param connection
* @param logger
* @return array of articles
* @throws SQLException
*/
public static Article[] getGoogleVideoArticles(Connection connection,
Logger logger) throws SQLException {
Article[] articlesToReturn = null;
PreparedStatement preparedStatement = ConnectionPool.prepareStatement(
"SELECT * FROM (" +
"SELECT " + Article.SQL_ARTICLE_COLUMNS +
"FROM editorial_articles a " +
"WHERE a.google_video_flg = 'Y' " +
"AND a.google_video_date is null " +
Article.LIVE_ARTICLES_SQL +
"ORDER BY a.article_date DESC" +
") ",
connection,
logger);
try {
//
// only ever get live articles and dont bother with images
//
articlesToReturn = Article.getArticles(preparedStatement,
false,
null,
false,
connection,
logger);
} finally {
preparedStatement.close();
}
return articlesToReturn;
}
/**
* Returns a LinkedHashMap with article ID --> <code>Articles</code> mapping.
* The return LinkedHashMap contains all articles retrieved from the database
* after executing prepared statement. <p> LinkedHashMap is used here as the
* order of articles returned is important. Articles can be ordered by date,
* headline or ranking when retrieved from the database and we need to
* maintain this order.<p>
*
* @param preparedStatement A prepared statement used to query database for
* a list of <code>Articles</code>.
* @param instantiateImages Specifieds whether images are instantiated
* @param usedBy The site for which articles are being requested. If
* set to null, then this method will return articles
* from all sites -- <b>regardless of whether they are
* shareable</b>. (This is only intended to be used on
* the back-end editorial tool to allow PTV editors to
* see articles from multiple sites simultaneously.)
* Otherwise return only articles written by the specified
* site.
* @param setRanking Ranking requires additional joins to the rank table
* and is only required a small percentage of the time.
* Consequently, most methods to return articles do not
* include the rank information. This flag determines
* whether the rank data is being returned by the
* prepared statement. If in doubt, set false.
* @param con A <code>Connection</code> to use in the method.
* @param logger A <code>Logger</code> to use in the method.
*
* @return Array of articles
*
* @exception SQLException if a database access error occurs
*/
private static Article[] getArticles(PreparedStatement preparedStatement,
boolean instantiateImages,
Site usedBy,
boolean setRanking,
Connection con,
Logger logger)
throws SQLException {
logger.debug("Getting a list of articles using prepared statement.");
//
// Return variable to hold all article beans.
//
Article[] articles = null;
Site site = usedBy==null?null:Site.getInstance(usedBy.getId());
ResultSet rs = ConnectionPool.executeQuery(preparedStatement, con, logger);
//
// Loop through results and build articles for each row returned.
// Images are retrieved in a separate step, as the linked query was taking
// too long to run.
//
ArrayList<Article> articleList = new ArrayList<Article>();
while (rs.next()) {
Article article = new Article();
article.setArticleFields(rs, site, setRanking, con, logger);
articleList.add(article);
}
/* FIXME: Fix properly
* The use of the usedBy parameter to represent a user scenario in which
* one article is selected on a particular page somewhere is awful since
* the caller of this method doesn't know how the usedBy parameter is being used
* by this method.
* Additionally, what if a caller passes a null usedBy and fetches multiple
* articles from the DB. The call to getLinkedObjects later would still fail.
* BEURK!!!
*/
if(site == null && articleList.size() == 1) {
site = articleList.get(0).getUsedBy();
}
//
// Add linked objects and images
//
if (articleList.size() > 0) {
articles = new Article[articleList.size()];
articles = articleList.toArray(articles);
//
// Images are only instantiated if required
//
if (instantiateImages) {
Article.getArticleImages(articles, con, logger);
}
//
// Linked objects are always instantiated.
//
Article.getLinkedObjects(articles, site, con, logger);
//
// Linked videos are always instantiated.
//
Article.getLinkedVideos(articles, site, con, logger);
//
// Owning videos are always instantiated
//
Article.getOwningVideos(articles, site, con, logger);
//
// Bind all articles with their associated pages. Linked objects are
// required to build links in several cases, so this is only done after
// linked objects are retrieved.
//
Article.setPages(articles, con, logger);
}
return articles;
}
/**
* Retrieves all external objects linked to articles.
*
* @param articles HashMap of articles for which linked objects are to be
* retrieved
* @param site Site
* @param dbConnection Database connection
* @param logger Logger
*/
private static void getLinkedObjects(Article[] articles,
Site site,
Connection dbConnection,
Logger logger)
throws SQLException {
StringBuffer idParams = new StringBuffer(2 * articles.length - 1);
for (int ii = 0; ii < articles.length; ii++) {
idParams.append((ii == 0 ? "" : ",") + "?");
}
PreparedStatement query = ConnectionPool.prepareStatement(
"SELECT detail_type_id, " +
"link_id, " +
"artl_id " +
"FROM article_links " +
"WHERE artl_id IN (" + idParams.toString() +") " +
"ORDER BY detail_type_id ",
dbConnection,
logger
);
try {
HashMap<Integer, Article> idToArticle = new HashMap<Integer, Article>();
for (int ii = 0; ii < articles.length; ++ii) {
query.setInt(ii + 1, articles[ii].getArticleId());
idToArticle.put(new Integer(articles[ii].getArticleId()), articles[ii]);
}
ResultSet rs = ConnectionPool.executeQuery(query, dbConnection, logger);
HashMap<DetailType, ArrayList<Integer>> detailTypeToLinkIds
= new HashMap<DetailType, ArrayList<Integer>>();
HashMap<HashKey, ArrayList<Article>> detailTypeAndLinkIdToArticles
= new HashMap<HashKey, ArrayList<Article>>();
ArrayList<Integer> linkIds = null;
//
// Build arraylists containing the instance ids for the various detail
// types
//
int previousDetailTypeId = -1;
DetailType detailType = null;
while (rs.next()) {
int linkId = rs.getInt("link_id");
int articleId = rs.getInt("artl_id");
int detailTypeId = rs.getInt("detail_type_id");
if (previousDetailTypeId != detailTypeId) {
detailType = DetailType.getDetailType(detailTypeId, dbConnection, logger);
linkIds = new ArrayList<Integer>();
detailTypeToLinkIds.put(detailType, linkIds);
}
linkIds.add(new Integer(linkId));
HashKey detailTypeAndLinkId = new HashKey(detailType, linkId);
ArrayList<Article> articleList = detailTypeAndLinkIdToArticles.get(detailTypeAndLinkId);
if (null == articleList) {
articleList = new ArrayList<Article>();
detailTypeAndLinkIdToArticles.put(detailTypeAndLinkId, articleList);
}
articleList.add(idToArticle.get(new Integer(articleId)));
previousDetailTypeId = detailTypeId;
}
//
// Cycle through the various detail types, retrieve the corresponding
// objects for each, and associate them with the articles.
//
Iterator<DetailType> iter = detailTypeToLinkIds.keySet().iterator();
while (iter.hasNext()) {
detailType = iter.next();
linkIds = detailTypeToLinkIds.get(detailType);
int[] ids = new int[linkIds.size()];
for (int jj = 0; jj < ids.length; ++jj) {
ids[jj] = (linkIds.get(jj)).intValue();
}
DetailObject[] articleLinks =
detailType.getDetailObjectInstances(ids, site, dbConnection, logger);
if (null != articleLinks) {
for (int kk = 0; kk < articleLinks.length; ++kk) {
int linkId = articleLinks[kk].getId();
ArrayList<Article> articleList = detailTypeAndLinkIdToArticles.get(
new HashKey(detailType, linkId)
);
for (int ll = 0; ll < articleList.size(); ++ll) {
articleList.get(ll).setLinkedObject(detailType, articleLinks[kk]);
}
}
}
}
} finally {
query.close();
}
}
/**
* Returns the number of articles for a given category and site.
*
* @param category Only articles in the category will be counted. Null
* for all articles.
* @param site The site to which articles belong
* @param startDate If present, only count articles after this date
* @param endDate If present, only count articles before this date
* @param liveArticlesOnly If true only live articles are counted
* @param homePageArticlesOnly if true only home page articles are counted
* @param excludeHomePageArticles if true home page articles are excluded.
* @param newslettersOnly If true only include articles marked for newseletter
* @param videoInclusion Determines whether articles with videos should be
* returned. This has one of three values:
* 1) WITH_VIDEOS only articles with videos are
* returned
* 2) WITHOUT_VIDEOS only articles without videos are
* returned
* 3) ALL_ARTICLES all articles regardless of whether
* they have videos
* @param dbConnection A <code>Connection</code> to use in the method.
* @param logger A <code>Logger</code> to use in the method.
*
* @return the number of articles in the category for the given site.
*
* @exception SQLException if a database access error occurs
*/
public static int getNumberOfArticles(Category category,
Site site,
Date startDate,
Date endDate,
boolean liveArticlesOnly,
boolean homePageArticlesOnly,
boolean excludeHomePageArticles,
boolean newslettersOnly,
int videoInclusion,
Connection dbConnection,
Logger logger)
throws SQLException {
int articlesInCategory = 0;
StringBuilder sqlQuery = new StringBuilder("SELECT COUNT(a.artl_id) AS article_count ");
sqlQuery.append("FROM editorial_articles a ");
sqlQuery.append("WHERE (a.orgn_club_id = ? OR a.general_flg = 'Y') ");
if(category != null) { sqlQuery.append("AND a.catg_id = ? "); }
if(liveArticlesOnly) { sqlQuery.append(Article.LIVE_ARTICLES_SQL); }
if(homePageArticlesOnly) { sqlQuery.append("AND a.hmpg_flg = 'Y' "); }
if(newslettersOnly) { sqlQuery.append("AND a.newsletter_flg = 'Y' "); }
if(endDate != null) { sqlQuery.append("AND a.article_date <= ? "); }
if(startDate != null) { sqlQuery.append("AND a.article_date >= ? "); }
sqlQuery.append(getVideoInclusionSQL(videoInclusion, "a."));
PreparedStatement query = ConnectionPool.prepareStatement(
sqlQuery.toString(), dbConnection, logger);
try {
int param = 1;
query.setInt(param++, site.getId());
if (null != category) {
query.setInt(param++, category.getCategoryId());
}
if (null != startDate) {
query.setDate(param++, new java.sql.Date(startDate.getTime()));
}
if (null != endDate) {
query.setDate(param++, new java.sql.Date(endDate.getTime()));
}
ResultSet rs = ConnectionPool.executeQuery(query, dbConnection, logger);
if (rs.next()) {
articlesInCategory = rs.getInt("article_count");
}
//
// Only one row should be returned
//
assert (!rs.next());
} finally {
query.close();
}
return articlesInCategory;
}
/**
* Returns the number of articles for a given category and site.
*
* @param category Only articles in the category will be counted. Null
* for all articles.
* @param site The site to which articles belong
* @param startDate If present, only count articles after this date
* @param endDate If present, only count articles before this date
* @param liveArticlesOnly If true only live articles are counted
* @param homePageArticlesOnly if true only home page articles are counted
* @param excludeHomePageArticles if true home page articles are excluded.
* @param newslettersOnly If true only include articles marked for newseletter
* @param dbConnection A <code>Connection</code> to use in the method.
* @param logger A <code>Logger</code> to use in the method.
*
* @return the number of articles in the category for the given site.
*
* @exception SQLException if a database access error occurs
*
* @deprecated Use {@link Article#getNumberOfArticles(Category, Site, Date, Date, boolean, boolean, boolean, boolean, int, Connection, Logger)}
* in preference.
*/
@Deprecated
public static int getNumberOfArticles(Category category,
Site site,
Date startDate,
Date endDate,
boolean liveArticlesOnly,
boolean homePageArticlesOnly,
boolean excludeHomePageArticles,
boolean newslettersOnly,
Connection dbConnection,
Logger logger)
throws SQLException {
return Article.getNumberOfArticles(category, site, startDate, endDate,
liveArticlesOnly, homePageArticlesOnly, excludeHomePageArticles,
newslettersOnly, Article.ALL_ARTICLES, dbConnection, logger);
}
/**
* Sets article fields for the given result set.
*
* @param rs A result set for which parameters are to be set.
* @param site Specifies the site which owns this article
* @param setRanking Ranking requires additional joins to the rank table
* and is only required a small percentage of the time.
* Consequently, most methods to return articles do not
* include the rank information. This flag determines
* whether the rank data is being returned by the
* prepared statement.
* @param con A <code>Connection</code> to use in the method.
* @param logger A <code>Logger</code> to use in the method.
*
* @exception SQLException if a database access error occurs
*/
protected void setArticleFields(ResultSet rs,
Site usedBy,
boolean setRanking,
Connection con,
Logger logger)
throws SQLException {
this.articleId = rs.getInt("artl_id");
//
// The site that owns this article can be retrieved from the database.
// This may, however, differ from the site that is calling this method so
// we need to set both sites here, even if they are the same.
//
this.ownedBy = Site.getInstance(rs.getInt("orgn_club_id"));
//
// There is a chance that this may be an article that we have created
// on behalf of one of the non-fli clubs. This is created in the article
// tool (/article/home.do) that we have supplied to the football league so
// that they can have news coverage for all of the teams in their leagues.
//
/*if(this.ownedBy == null) {
this.ownedBy = Site.getInstance(rs.getInt("orgn_club_id"));
}*/
//
// If we have not been supplied with the site that is using the article
// (usually only when we are listing articles for PTV editors on the
// editorial tool) then we set the user to be the same as the owner.
//
this.usedBy = (null == usedBy ? this.ownedBy : usedBy);
this.category = Category.getInstance(rs.getInt("catg_id"), con, logger);
//
// Set the ranking only if instructed to do so (most queries will not
// return the rank information as this results in often unnecessary
// overhead.
//
if (setRanking) {
int rank = rs.getInt("page_rank");
this.setPageRank(rs.wasNull() ? NOT_RANKED : rank);
rank = rs.getInt("home_page_rank");
this.setHomePageRank(rs.wasNull() ? NOT_RANKED : rank);
}
//
// Retrieve Dates. Note that site posted date may be null;
//
this.setArticleDate(
ConnectionPool.getDateAndTime(rs, "article_date")
);
this.lastEditDate = ConnectionPool.getDateAndTime(rs, "last_edit_date");
this.setSitePostedDate(
ConnectionPool.getDateAndTime(rs, "site_posted_date")
);
this.setSiteRemovedDate(
ConnectionPool.getDateAndTime(rs, "site_removed_date")
);
this.setSyndicatedTimeStamp(
ConnectionPool.getDateAndTime(rs, "syndication_timestamp")
);
this.setLockDate(
ConnectionPool.getDateAndTime(rs, "lock_date")
);
this.lockId = rs.getInt("lock_id");
this.setUnSyndicatedTimeStamp(
ConnectionPool.getDateAndTime(rs, "unsyndicated_date")
);
//
// get the url for the article
//
this.setUrl(rs.getString("url"));
logger.debug(
"Article date: " + this.getArticleDate() +
", Last edit date: " + this.getLastEditDate() +
", Site posted date: " + this.getSitePostedDate()
);
//
// Get article parts.
// .:TBC:. Consider creating a ConnectionPool method to simply getting
// Clobs
//
this.setHeadline(rs.getString("headline"));
this.setTeaser(rs.getString("teaser"));
this.setSummary(rs.getString("summary"));
Clob body = rs.getClob("body");
if (body != null) {
this.setBody(body.getSubString(1, (int) body.length()));
}
this.setIsGeneralArticle(
"Y".equals(rs.getString("general_flg"))
);
this.setOnHomePage("Y".equals(rs.getString("hmpg_flg")));
this.setDisplayOnNewsletter(("Y").equals(rs.getString("newsletter_flg")));
this.setAvailableForAlerts(
"Y".equals(rs.getString("desktop_alert_flg")));
this.headerImageId = rs.getInt("header_image_id");
if (rs.wasNull()) {
this.headerImageId = Article.NO_IMAGE_ID;
}
this.teaserImageId = rs.getInt("teaser_image_id");
if (rs.wasNull()) {
this.teaserImageId = Article.NO_IMAGE_ID;
}
this.mobileImageId = rs.getInt("mobile_image_id");
if (rs.wasNull()) {
this.mobileImageId = Article.NO_IMAGE_ID;
}
this.videoHoldingImageId = rs.getInt("video_holding_image_id");
if (rs.wasNull()) {
this.videoHoldingImageId = Article.NO_IMAGE_ID;
}
this.setSyndicated("Y".equals(rs.getString("syndicated_flg")));
this.setAvailableForAlerts(
"Y".equals(rs.getString("desktop_alert_flg"))
);
this.setAudioStream(rs.getString("audio_stream"));
this.setVideoLo(rs.getString("video_lo"));
this.setVideoMedium(rs.getString("video_medium"));
this.setVideoHi(rs.getString("video_hi"));
this.setVideoDownload(rs.getString("video_download"));
this.setVideoPlayCount(rs.getInt("video_play_count"));
this.setMobileArticle("Y".equals(rs.getString("mobile_site_flg")));
this.setPodcastArticle("Y".equals(rs.getString("podcasting_flg")));
this.setGoogleVideoArticle("Y".equals(rs.getString("google_video_flg")));
this.setGoogleVideoProcessedDate(
ConnectionPool.getDateAndTime(rs, "google_video_date")
);
this.setFlashFile(rs.getString("flash_file"));
this.setTeaserFlashFile(rs.getString("teaser_flash_file"));
this.setAvailableInPlayer(StringUtils.toBool(rs.getString("available_in_player_flg")));
Clob flashScript = rs.getClob("flash_script");
if (flashScript != null) {
this.setFlashScript(
flashScript.getSubString(1, (int) flashScript.length())
);
}
Clob flashTeaserScript = rs.getClob("flash_teaser_script");
if (flashTeaserScript != null) {
this.setFlashTeaserScript(
flashTeaserScript.getSubString(1, (int) flashTeaserScript.length())
);
}
if (this.ownedBy.getSiteSearchConfig().isDetailTypeCategorizationEnabled()) {
this.detailObjectMetaInformation.setDetailTypeCategories(
DetailTypeCategory.getDetailObjectCategories(this, con, logger)
);
}
}
/**
* For each article search for a page capable of displaying it. This will
* most often involve looking for the page which can display the article
* directly, but if no such page is found, the objects linked to the article
* will be used.
*
* Note that this method must be called after objects associated with the
* article have been instantiated, since these linked objects may be used
* to retrieve the page.
*
* @param articles Array of articles
* @param dbConnection database connection
* @param logger Logger
*/
private static void setPages(Article[] articles,
Connection dbConnection,
Logger logger)
throws SQLException{
logger.debug("Setting pages for articles");
for (int ii = 0; ii < articles.length; ++ii) {
articles[ii].setPageForSite(articles[ii].getUsedBy(), dbConnection, logger);
}
}
/**
* Find a page that can be suitably used to display this article on another
* site.
*
* <p><em>Note:</em> This will not update the internal state of this article.</p>
*
* @param site The {@link Site} to find the page for.
* @param dbConnection The {@link Connection} to use to query the database.
* @param logger The {@link Logger} to use.
*
* @return A {@link Page} that could suitably be used to display this article
*
* @throws SQLException
*/
public PageReference getPageForSite(final Site site,
final Connection dbConnection,
final Logger logger)
throws SQLException {
PageReference pageRef = null;
Page sitePage;
//
// Get the Page used to display this Article. If no article detail Page is
// found to display this Article, check whether any of the objects linked
// to the article may be used to construct a link
//
// Note that all the Pages for a site are cached so the overhead from
// running getPage several times is minimal.
//
sitePage = Page.getPage(this.getCategory(), PageElement.DETAIL_TYPE_ARTICLE_ID, site, dbConnection, logger);
if (sitePage != null) {
pageRef = new PageReference(sitePage, this);
} else {
//
// Loop through linked objects looking for matching pages
//
Iterator<String> iter = this.linkedObjects.keySet().iterator();
logger.debug(String.format("Article %d has %d object(s)", this.articleId,
this.linkedObjects.size()));
while (iter.hasNext() && (sitePage == null)) {
List<DetailObject> detailObjects = this.linkedObjects.get(iter.next());
DetailObject linkedObject = null;
if (!ListUtils.isNullOrEmpty(detailObjects)) {
linkedObject = detailObjects.get(0);
logger.debug(String.format("Searching for page to display category %d " +
"and detail type %d", this.getCategory().getCategoryId(),
linkedObject.getDetailTypeId()));
sitePage = Page.getPage(this.getCategory(), linkedObject.getDetailTypeId(),
site, dbConnection, logger);
if (sitePage != null) {
pageRef = new PageReference(sitePage, linkedObject);
break;
}
}
}
}
return pageRef;
}
/**
* Update an article to apply a page suitable for displaying the supplied
* article. This involves searching for a page that can either display an
* article detail directly, or a page that can display one of the linked
* objects.
*
* <p><em>Note:</em> This should generally only be called <em>immediately
* after</em> instantiation of an article. Generally you should not call it
* otherwise. However, in the case of Syndicated articles, we sometimes need
* to update these page details after the fact.</p>
*
* @see ptv.page.CustomArticleIndex#up
*
* @param site The {@link Site} to search for the {@link Page} to use to
* display the article.
* @param dbConnection The {@link Connection} to use to find the page.
* @param logger The {@link Logger} to use.
*
* @throws SQLException If there was a problem searching for the page.
*/
public void setPageForSite(final Site site,
final Connection dbConnection,
final Logger logger)
throws SQLException {
final PageReference pageRef = getPageForSite(site, dbConnection, logger);
if(pageRef != null) {
this.page = pageRef.getPage();
this.detailObjectForCurl = pageRef.getLinkedObject();
this.curlId = pageRef.getLinkedObject().getId();
} else if(logger.isDebugEnabled()) {
logger.debug(String.format("Could not find a page on site %d suitable for article %d?!?!", site.getId(), this.articleId));
}
if(this.isAvailableInPlayer()) {
this.setPlayerNewsPage(dbConnection);
}
}
/**
* Update the article (in memory) to use a particular site for the article page.
*
* FIXME This is blindingly horrible. It owes its existence to the inconsistent
* behaviour between {@link Article#getOwnedBy()}, {@link Article#getUsedBy()}
* and syndication.
* In the case of syndication, the ID of the site using the article is
* hidden from the lower layer (ie DAO) code (usedBy is passed as null).
* So the code makes an assumption that usedBy == ownedBy.
* In order to generate valid URL's for sites with syndicated articles
* we need to know which site is displaying the article. Given that this
* is not available to syndicated articles (properly) we do this post
* processing phase only in the indexes where it can cause a problem.
*
* FIXME Article.ownedBy and Article.usedBy behaviour is inconsistent and
* causes large amounts of grief.
*
* NOTE: If you're seeing odd article behaviour on your indexes, it's most
* likely because you have {@link #useSyndicationPartnerPage} set to {@code false}
* and this code is kicking you in the cods.
*
* NOTE: This will ONLY occur if {@link #useSyndicationPartnerPage} is {@code false}.
* This is solely to maintain existing behaviour. I'm getting tired of
* this.
*
* @param site The {@link Site} to search for appropriate page for the specified
* article.
* @param dbConnection The {@link Connection} to use to find page to display
* the indexes article.
* @param logger The {@link Logger} to use.
*/
public void updateSitePage(final Site site,
final Connection dbConnection,
final Logger logger)
throws SQLException {
if(!this.getUsedBy().equals(site)) {
// Update the used by on the article
this.setUsedBy(site);
// Find an appropriate page for the article
this.setPageForSite(site, dbConnection, logger);
}
}
/**
* Saves the current article.
*
* This method may be called on either a new article, that is an article
* that has not been written to the database or an existing article. In the
* case of the former, new data will be inserted into the database, and for
* the latter existing data will be updated.
*
* This method updates the article's last edit date, and also sets the
* article date if this field is null.
*
* Note that this method does not flush associated pages.
*
* @param dbConnection The database connection.
* @param logger The error logger.
*
* @exception SQLException Thrown if a database error occurs.
*
* @deprectated Replaced by {@link ##save(boolean, Connection, Logger)}
* @see #save(boolean, Connection, Logger)
*/
public void save(Connection dbConnection, Logger logger)
throws SQLException,
URISyntaxException,
InvalidCurlException {
this.save(false, dbConnection, logger);
}
/**
* Saves the current article.
*
* This method may be called on either a new article, that is an article
* that has not been written to the database or an existing article. In the
* case of the former, new data will be inserted into the database, and for
* the latter existing data will be updated.
*
* This method updates the article's last edit date, and also sets the
* article date if this field is null.
*
* @param flushPages If true, the pages where this article displays will
* be flushed.
* @param dbConnection The database connection.
* @param logger The error logger.
*
* @exception SQLException Thrown if a database error occurs.
*/
public boolean save(boolean flushPages, Connection dbConnection, Logger logger)
throws SQLException,
URISyntaxException,
InvalidCurlException {
logger.log(
Level.DEBUG,
"Entering Article.save() method. Article date: " +
this.articleDate + ", Last edit date: " +
this.lastEditDate + ", Site posted date: " +
this.sitePostedDate
);
//
// Article date is mandatory. If it's not present, set it to the current
// date.
//
if (null == this.articleDate) {
this.articleDate = new Date();
}
//
// Indicator used by the finally block to determine whether the save
// succeeded.
//
boolean saveSucceeded = false;
//
// Indicator used to determine whether to use insert or update statement
// on articles table. No article id means that this is a new article and
// will have to inserted into the database.
//
boolean newArticle = (Article.NEW_ARTICLE == this.articleId);
//
// Ensure the last edit date is updated when the article content changes.
//
// The last edit date is not updated in all circumstances, for instance
// when an article is reranked, as changing the last edit date will cause
// a syndicated article to be resent with an update (not required when
// ranking changes).
//
if (this.updateLastEditDate || newArticle) {
this.lastEditDate = new Date();
}
//
// Prepared statment used for inserts and updates.
//
PreparedStatement query;
PreparedStatement queryUpdate;
if (newArticle) {
//
// Get the next article id from the article Id sequence.
//
this.articleId = ConnectionPool.getNextSequenceNumber("artl_seq",
dbConnection,
logger);
logger.log(
Level.DEBUG,
"Saving a new article with articleId: " + this.articleId
);
query = ConnectionPool.prepareStatement(
"INSERT INTO editorial_articles (" +
"body, " +
"headline, " +
"teaser, " +
"summary, " +
"article_date, " +
"orgn_club_id, " +
"general_flg, " +
"catg_id, " +
"lang_cd, " +
"last_edit_date, " +
"site_posted_date, " +
"site_removed_date, " +
"hmpg_flg, " +
"header_image_id, " +
"teaser_image_id, " +
"mobile_image_id, " +
"video_holding_image_id, " +
"audio_stream, " +
"video_lo, " +
"video_medium, " +
"video_hi, " +
"video_download, " +
"video_play_count, " +
"url, " +
"syndicated_flg, " +
"mobile_site_flg, " +
"podcasting_flg, " +
"google_video_flg, " +
"flash_file, " +
"teaser_flash_file, " +
"flash_script, " +
"flash_teaser_script, " +
"desktop_alert_flg, " +
"syndication_timestamp, " +
"lock_id," +
"lock_date," +
"unsyndicated_date, " +
"launched_flg, " +
"expired_flg, " +
"newsletter_flg, " +
"available_in_player_flg, " +
"artl_id " +
") VALUES (" +
"EMPTY_CLOB(), " +
"?, " +
"?, " +
"?, " +
"?, " +
"?, " +
"?, " +
"?, " +
"'EN', " +
"?, " +
"?, " +
"?, " +
"?, " +
"?, " +
"?, " +
"?, " +
"?, " +
"?, " +
"?, " +
"?, " +
"?, " +
"?, " +
"?, " +
"?, " +
"?, " +
"?, " +
"?, " +
"?, " +
"?, " +
"?, " +
"EMPTY_CLOB(), " +
"EMPTY_CLOB(), " +
"?, " +
"?, " +
"NULL, " +
"NULL, " +
"NULL, " +
"?, " +
"?, " +
"?, " +
"?, " +
"? " +
")",
dbConnection,
logger
);
} else {
//
// The article already exists. Perform an update rather than an insert.
//
logger.log(
Level.DEBUG,
"Updating existing article with articleId = " + this.articleId
);
//
// Note in the following query that lock_date is always set to null
// this is important to the behaviour of the articleSyndicxator
// application which uses this field - please do not change it
//
query = ConnectionPool.prepareStatement(
"UPDATE editorial_articles " +
"SET body = EMPTY_CLOB(), " +
"headline = ?, " +
"teaser = ?, " +
"summary = ?, " +
"article_date = ?, " +
"orgn_club_id = ?, " +
"general_flg = ?, " +
"catg_id = ?, " +
"lang_cd = 'EN', " +
"last_edit_date = ?, " +
"site_posted_date = ?, " +
"site_removed_date = ?, " +
"hmpg_flg = ?, " +
"header_image_id = ?, " +
"teaser_image_id = ?, " +
"mobile_image_id = ?, " +
"video_holding_image_id = ?, " +
"audio_stream = ?, " +
"video_lo = ?, " +
"video_medium = ?, " +
"video_hi = ?, " +
"video_download = ?, " +
"video_play_count = ?, " +
"url = ?, " +
"syndicated_flg = ?, " +
"mobile_site_flg = ?, " +
"podcasting_flg = ?, " +
"google_video_flg = ?, " +
"flash_file = ?, " +
"teaser_flash_file = ?, " +
(this.ownedBy.getEnabledManualFlashScriptInsert()?
"flash_script = EMPTY_CLOB(), ":"") +
(this.ownedBy.getEnabledManualFlashScriptInsert() &&
this.ownedBy.getEnableTeaserGames()?
"flash_teaser_script = EMPTY_CLOB(), ":"") +
"desktop_alert_flg = ?, " +
"syndication_timestamp = ?, " +
"lock_id = ?," +
"lock_date = null," +
"unsyndicated_date = ?, " +
"launched_flg = ?, " +
"expired_flg = ?, " +
"newsletter_flg = ?, " +
"available_in_player_flg = ? " +
"WHERE artl_id = ? ",
dbConnection,
logger
);
}
//
// Prepared statements used for handling body clob;
//
PreparedStatement retrieveClobQuery = ConnectionPool.prepareStatement(
"SELECT body " +
"FROM editorial_articles " +
"WHERE artl_id = ? ",
dbConnection,
logger
);
PreparedStatement updateClobQuery = ConnectionPool.prepareStatement(
"UPDATE editorial_articles " +
"SET body = ? " +
"WHERE artl_id = ? ",
dbConnection,
logger
);
try {
//
// Autocommit needs to be off while the clob stuff is being done
//
dbConnection.setAutoCommit(false);
int param = 0;
query.setString(++param, this.headline);
query.setString(++param, this.teaser);
query.setString(++param, this.summary);
ConnectionPool.setDateAndTime(query, ++param, this.articleDate);
query.setInt(++param, this.ownedBy.getId());
query.setString(++param, this.generalArticle ? "Y" : "N");
query.setInt(++param, this.category.getCategoryId());
ConnectionPool.setDateAndTime(query, ++param, this.lastEditDate);
ConnectionPool.setDateAndTime(query, ++param, this.sitePostedDate);
ConnectionPool.setDateAndTime(query, ++param, this.siteRemovedDate);
query.setString(++param, this.onHomePage ? "Y" : "N");
if (Article.NO_IMAGE_ID == this.headerImageId) {
query.setNull(++param, java.sql.Types.INTEGER);
} else {
query.setInt(++param, this.headerImageId);
}
if (Article.NO_IMAGE_ID == this.teaserImageId) {
query.setNull(++param, java.sql.Types.INTEGER);
} else {
query.setInt(++param, this.teaserImageId);
}
if (Article.NO_IMAGE_ID == this.mobileImageId) {
query.setNull(++param, java.sql.Types.INTEGER);
} else {
query.setInt(++param, this.mobileImageId);
}
if (Article.NO_IMAGE_ID == this.videoHoldingImageId) {
query.setNull(++param, java.sql.Types.INTEGER);
} else {
query.setInt(++param, this.videoHoldingImageId);
}
query.setString(++param, this.audioStream);
query.setString(++param, this.videoLo);
query.setString(++param, this.videoMedium);
query.setString(++param, this.videoHi);
query.setString(++param, this.videoDownload);
query.setInt(++param, this.videoPlayCount);
query.setString(++param, this.url);
query.setString(++param, this.syndicated ? "Y" : "N");
query.setString(++param, this.mobileArticle ? "Y" : "N");
query.setString(++param, this.podcastArticle ? "Y" : "N");
query.setString(++param, this.googleVideoArticle ? "Y" : "N");
query.setString(++param, this.flashFile);
query.setString(++param, this.teaserFlashFile);
query.setString(++param, this.availableForAlerts ? "Y" : "N");
ConnectionPool.setDateAndTime(query, ++param, this.syndicatedTimeStamp);
//
// For the lock_id and unSyndicated timestamp we only need to set
// their values when updating - they are always null on insert
//
if (!newArticle) {
if (Article.NO_LOCK_ID == this.lockId) {
query.setNull(++param, java.sql.Types.INTEGER);
} else {
query.setInt(++param, this.lockId);
}
ConnectionPool.setDateAndTime(query,
++param,
this.unSyndicatedTimeStamp);
}
//
// Set the launched and expiry flags. These are used by the article
// flusher to handle articles that have been scheduled for launch or
// expiration.
//
query.setString(++param, this.isLive() ? "Y" : "N");
query.setString(++param, this.isExpired() ? "Y" : "N");
query.setString(++param, this.displayOnNewsletters ? "Y" : "N");
query.setString(++param, StringUtils.toBoolYNString(isAvailableInPlayer()));
query.setInt(++param, this.articleId);
logger.log(Level.DEBUG,
"Executing query to create/update article: " + this.articleId);
//
// Add or Update the article.
//
ConnectionPool.executeUpdate(query, dbConnection, logger);
//
// Close the query before reusing it.
//
query.close();
//
// Update the article body - clobs cannot be updated directly and so
// must be handled seperately.
//
logger.log(Level.DEBUG, "Updating article body for " + articleId);
//
// Clobs can not be updated or inserted directly, instead a bit of
// jiggery pokery must take place first. The empty clob inserted.
// updated above is retrieved from the database, populated with the
// correct data and then re-inserted into the database.
//
retrieveClobQuery.setInt(1, articleId);
ResultSet resultSet = ConnectionPool.executeQuery(
retrieveClobQuery,
dbConnection,
logger
);
resultSet.next();
Clob dataClob = resultSet.getClob("body");
//
// Only one row should be retrieved
//
assert (resultSet.isFirst() && !resultSet.next());
//
// Populate Clob with new data and insert it back into the
// database.
//
//
// Modification DL 11/2006
// Context linking
// We look in the body of the article for keywords that
// have been defined for this site, and replace them by the
// link that has been configured
//
//
// Retrieve all context links for this site
//
ContextLink[] contextLinks = ContextLink.getContextLinkForSite(this.getOwnedBy(), dbConnection, logger);
if (contextLinks != null) {
for (int i=0; i<contextLinks.length; i++) {
//
// Building the link
//
String link = null;
//
// The name of the window will be the keyword, but we need to replace spaces
// by underscores to avoid JS error in IE6
//
String keyword = contextLinks[i].getKeyword();
if (contextLinks[i].isInNewWindow()) {
link = "<a class=\"contextLink\" href=\"#\" onclick='window.open(\"" + contextLinks[i].getLink() +
"\",\"" + keyword.replaceAll(" ","_").replace("'","_").replace("&amp;","_") +"\",\"height=" +
contextLinks[i].getWindowHeight() + ", width=" + contextLinks[i].getWindowWidth() +
",scrollbars=1,resizable=1,status=1,toolbar=1,location=1\");return false;'" + ">$2</a>";
//$2 is the second matched group - i.e. the keyword we're replacing.
}
else {
link = "<a class=\"contextLink\" href=\"" + contextLinks[i].getLink() + "\"" +
">$2</a>";
//$2 is the second matched group - i.e. the keyword we're replacing.
}
KeywordLinksUtility utility = new KeywordLinksUtilityImpl();
this.body = utility.replaceKeywordsWithLinks(this.body, keyword, link);
// this.body = this.body.replaceAll(" ((?i)" +
// contextLinks[i].getKeyword() + ") ",
// " <a href=\""+contextLinks[i].getLink()+"\" target=\"" +
// contextLinks[i].getTargetFrame() +"\">$1</a> ");
}
}
dataClob.setString(1, this.body);
updateClobQuery.setClob(1, dataClob);
updateClobQuery.setInt(2, articleId);
ConnectionPool.executeUpdate(updateClobQuery,
dbConnection,
logger);
//
// If the site to which the article belongs allows for manual
// insertion of flash scripts and there is a flash script
// present, update the proper clob.
//
if (this.ownedBy.getEnabledManualFlashScriptInsert() &&
this.flashScript != null &&
!"".equals(this.flashScript.trim())) {
retrieveClobQuery.close();
updateClobQuery.close();
retrieveClobQuery = ConnectionPool.prepareStatement(
"SELECT flash_script " +
"FROM editorial_articles " +
"WHERE artl_id = ? ",
dbConnection,
logger
);
updateClobQuery = ConnectionPool.prepareStatement(
"UPDATE editorial_articles " +
"SET flash_script = ? " +
"WHERE artl_id = ? ",
dbConnection,
logger
);
retrieveClobQuery.setInt(1, articleId);
resultSet = ConnectionPool.executeQuery(
retrieveClobQuery,
dbConnection,
logger
);
if (resultSet.next()) {
dataClob = resultSet.getClob("flash_script");
dataClob.setString(1, this.flashScript);
updateClobQuery.setClob(1, dataClob);
updateClobQuery.setInt(2, articleId);
}
}
//
// If the site to which the article belongs allows for manual
// insertion of flash scripts and teaser functionality is enabled
// and there is a teaser flash script present, update the proper clob.
//
if (this.ownedBy.getEnabledManualFlashScriptInsert() &&
this.ownedBy.getEnableTeaserGames() &&
this.flashTeaserScript != null &&
!"".equals(this.flashTeaserScript.trim())) {
retrieveClobQuery.close();
updateClobQuery.close();
retrieveClobQuery = ConnectionPool.prepareStatement(
"SELECT flash_teaser_script " +
"FROM editorial_articles " +
"WHERE artl_id = ? ",
dbConnection,
logger
);
updateClobQuery = ConnectionPool.prepareStatement(
"UPDATE editorial_articles " +
"SET flash_teaser_script = ? " +
"WHERE artl_id = ? ",
dbConnection,
logger
);
retrieveClobQuery.setInt(1, articleId);
resultSet = ConnectionPool.executeQuery(
retrieveClobQuery,
dbConnection,
logger
);
if (resultSet.next()) {
dataClob = resultSet.getClob("flash_teaser_script");
dataClob.setString(1, this.flashTeaserScript);
updateClobQuery.setClob(1, dataClob);
updateClobQuery.setInt(2, articleId);
}
}
//
// Ssve also the detail type categories associated to this article.
//
if (this.ownedBy.getSiteSearchConfig().isDetailTypeCategorizationEnabled()) {
DetailTypeCategory.addCategoriesToDetailObject(
this,
this.getDetailObjectMetaInformation().getDetailTypeCategories(),
dbConnection,
logger
);
}
//
// Note by GFE Jun 2005
// We've had some subtle problems with the editorial_articles , ranks and
// article_keywords tables getting conflicting locks and deadlocks
// on them. Diagnosing exactly what is wrong is tricky.
// Supposition is that previously we used to keep autocommit off
// and therefore hold locks on rows in the 3 tables for long periods.
// Referntial integrity between article_keywords ranks and
// editorial_articles means that is process A on STG05 updates articleid 3
// in a long uncommitted chain than if someone does the same save on
// articleId 3 on stg06 then you start to get into knots.
// The process is unclear to me still, even though i've managed
// to reproduce it on dev by making the ranks and article_keywords tables use
// their own uncommitting DB connections to do their updates (this causes
// the exact same locking errors on dev immediately).
// As a solution / workaround I've elected to turn autommit on for
// the main connection on the understanding that this dramitically reduces
// the timeframe for conficts to occur. I don't know for sure that this
// will work but having created a similar errors on dev by using 3
// seperate DBConnections (one for each table) turning on automcommit
// stopped the errors - so its worth a shot.
//
dbConnection.setAutoCommit(true);
//
// If the page category has changed the article needs to be unranked
// from it's old category.
//
if (!newArticle &&
null != this.priorCategory &&
this.category != this.priorCategory &&
(this.pageRank != NOT_RANKED || this.pageRankUpdated)) {
this.setRank(CATEGORY_RANK_TYPE,
NOT_RANKED,
this.priorCategory,
dbConnection,
logger);
}
//
// Update the page ranking for this article. New articles that have
// their rank set to NOT_RANKED do not need to be processed.
//
// Also needs to be updated if the category has changed since the article
// must be ranked for the new category.
//
if ((!newArticle &&
null != this.priorCategory &&
this.category != this.priorCategory &&
this.pageRank != NOT_RANKED) ||
(this.pageRankUpdated && (this.pageRank != NOT_RANKED || !newArticle ))) {
this.setRank(CATEGORY_RANK_TYPE,
this.pageRank,
this.category,
dbConnection,
logger);
}
//
// Update the home page ranking for this article. New articles that have
// their rank set to NOT_RANKED do not need to be processed.
//
if (this.homePageRankUpdated &&
(this.homePageRank != NOT_RANKED || !newArticle)) {
this.setRank(HOME_CATEGORY_RANK_TYPE,
this.homePageRank,
null,
dbConnection,
logger);
}
//
// Update the related article keywords. These must be explictly
// instantiated by the getRelatedArticleKeywords method. If the array
// of keywords is null, it implies that the keywords have not been changed
// and no action needs be taken here.
//
if (null != this.relatedArticleKeywords) {
query.close();
//
// Delete any existing keywords before reinserting them. This is less
// work that trying to work out what may have changed.
//
query = ConnectionPool.prepareStatement(
"DELETE FROM article_keywords " +
"WHERE artl_id = ? " +
"AND orgn_id = ? ",
dbConnection,
logger
);
query.setInt(1, this.articleId);
query.setInt(2, this.usedBy.getId());
ConnectionPool.executeUpdate(query, dbConnection, logger);
query.close();
//
// Insert each keyword back into the database.
//
query = ConnectionPool.prepareStatement(
"INSERT INTO article_keywords (" +
"keyword, orgn_id, artl_id , rank " +
") VALUES (?, ?, ?, ?) ",
dbConnection,
logger
);
query.setInt(2, this.usedBy.getId());
query.setInt(3, this.articleId);
HashSet<String> keywordSet = new HashSet<String>();
String[] keyRank = new String[2];
for (int ii = 0; ii < this.relatedArticleKeywords.length; ++ii) {
//
// Replace contiguous white space with a single space, in case
// multiple spaces have been accidentally added between words, such
// as player names
//
String keyword =
this.relatedArticleKeywords[ii].replaceAll("\\s+", " ");
//
// Trim white space around the keyword and convert to lower case.
//
keyword = keyword.trim().toLowerCase();
//
// Don't allow insertion of empty or duplicate keywords
//
if (!keyword.equals("") && !keywordSet.contains(keyword)) {
//
// Update all articles with the same Orgn_id and same
// keyword as having the same rank.
//
queryUpdate = null;
try {
queryUpdate = ConnectionPool.prepareStatement(
"UPDATE article_keywords " +
"SET rank = ? " +
"WHERE keyword = ? " +
"AND orgn_id = ? " ,
dbConnection,
logger
);
query.setString(1, keyword);
query.setNull(4, java.sql.Types.NUMERIC);
queryUpdate.setNull(1, java.sql.Types.NUMERIC);
queryUpdate.setString(2, keyword);
queryUpdate.setInt(3, this.usedBy.getId());
ConnectionPool.executeUpdate(queryUpdate, dbConnection, logger);
ConnectionPool.executeUpdate(query, dbConnection, logger);
keywordSet.add(keyword);
} finally {
if(queryUpdate != null) {
queryUpdate.close();
}
}
}
}
}
//
// Update the teaser videos for the article.
//
if (null != this.teaserVideos) {
query.close();
//
// Delete any existing teaser videos before reinserting them. This is less
// work that trying to work out what may have changed.
//
query = ConnectionPool.prepareStatement(
"DELETE FROM article_teaser_videos " +
"WHERE artl_id = ? ",
dbConnection,
logger
);
query.setInt(1, this.articleId);
ConnectionPool.executeUpdate(query, dbConnection, logger);
query.close();
//
// Insert each teaser video back into the database.
//
query = ConnectionPool.prepareStatement(
"INSERT INTO article_teaser_videos (" +
"teaser_video, artl_id " +
") VALUES (?, ?) ",
dbConnection,
logger
);
query.setInt(2, this.articleId);
for (int ii = 0; ii < this.teaserVideos.length; ++ii) {
String videoPath = this.teaserVideos[ii];
//
// Trim white space around the video path and convert to lower case.
//
videoPath = videoPath.trim().toLowerCase();
//
// Don't allow insertion of empty paths
//
if (!videoPath.equals("")) {
query.setString(1, videoPath);
ConnectionPool.executeUpdate(query, dbConnection, logger);
}
}
}
query.close();
//
// Save the linked content associated with the article
//
// Delete any existing links before reinserting them. This is less
// work that trying to work out what may have changed.
//
query = ConnectionPool.prepareStatement(
"DELETE FROM article_links " +
"WHERE artl_id = ? ",
dbConnection,
logger
);
query.setInt(1, this.articleId);
ConnectionPool.executeUpdate(query, dbConnection, logger);
query.close();
if (this.linkedObjects.size() > 0) {
//
// Insert links back into the database.
//
query = ConnectionPool.prepareStatement(
"INSERT INTO article_links (" +
"artl_id, detail_type_id, link_id " +
") VALUES (?, ?, ?) ",
dbConnection,
logger
);
query.setInt(1, this.getArticleId());
Iterator<String> iter = this.linkedObjects.keySet().iterator();
while (iter.hasNext()) {
List<DetailObject> detailObjects = this.linkedObjects.get(iter.next());
if (!ListUtils.isNullOrEmpty(detailObjects)) {
for (DetailObject detailObject : detailObjects) {
query.setInt(2, detailObject.getDetailTypeId());
query.setInt(3, detailObject.getId());
ConnectionPool.executeUpdate(query, dbConnection, logger);
}
}
}
query.close();
}
//
// Save images linked to the article.
//
if (this.usedBy.isArticleImageLinksEnabled()) {
//
// Delete any existing links before reinserting them. This is less
// work that trying to work out what may have changed.
//
query = ConnectionPool.prepareStatement(
"DELETE FROM article_images " +
"WHERE article_id = ? ",
dbConnection,
logger
);
query.setInt(1, this.articleId);
ConnectionPool.executeUpdate(query, dbConnection, logger);
query.close();
if (null != this.linkedImages && this.linkedImages.length > 0) {
//
// Insert links back into the database.
//
query = ConnectionPool.prepareStatement(
"INSERT INTO article_images (" +
"article_id, image_id, seq_no " +
") VALUES (?, ?, ?) ",
dbConnection,
logger
);
query.setInt(1, this.getArticleId());
for (int ii = 0; ii < this.linkedImages.length; ++ii) {
query.setInt(2, this.linkedImages[ii].getId());
query.setInt(3, ii + 1);
ConnectionPool.executeUpdate(query, dbConnection, logger);
}
query.close();
}
}
//
// Save videos linked to the article.
//
if (this.usedBy.isArticleVideoLinksEnabled()) {
//
// Delete any existing links before reinserting them. This is less
// work that trying to work out what may have changed.
//
query = ConnectionPool.prepareStatement(
"DELETE FROM article_videos " +
"WHERE article_id = ? ",
dbConnection,
logger
);
query.setInt(1, this.articleId);
ConnectionPool.executeUpdate(query, dbConnection, logger);
query.close();
if (null != this.linkedVideos && this.linkedVideos.length > 0) {
//
// Insert links back into the database.
//
query = ConnectionPool.prepareStatement(
"INSERT INTO article_videos (" +
"article_id, video_id, seq_no " +
") VALUES (?, ?, ?) ",
dbConnection,
logger
);
query.setInt(1, this.getArticleId());
for (int ii = 0; ii < this.linkedVideos.length; ++ii) {
query.setInt(2, this.linkedVideos[ii].getId());
query.setInt(3, ii + 1);
ConnectionPool.executeUpdate(query, dbConnection, logger);
}
query.close();
}
}
//
// Mark the article as needing Lucene indexing.
//
// Only if live (to add to index) or expired (to remove from index).
// Articles ready but not yet launched do not need to be indexed yet.
//
if (this.ownedBy != null && (this.isLive() || this.isExpired())) {
LuceneUtils.markForLuceneIndexing(
this.ownedBy.getId(),
this.getDetailTypeId(),
this.articleId,
this.isExpired(),
false, // add to the index even if it wasn't there before
dbConnection,
logger
);
}
saveSucceeded = true;
//
// After the article has been saved, flush the affected pages. Placed
// after saveSucceeded to ensure the article is commited even if there's
// a problem flushing it.
//
if (flushPages) {
this.flush(dbConnection, logger);
}
//
// We have to update the news sitemap for this site, if enabled
//
// We catch the exception and log it, since failing to register the sitemap
// to be updated is not a failure of the article save.
//
if (this.isLive() && this.getOwnedBy().getSitemapConfig().isNewsSitemapEnabled()) {
try {
SiteMapUtils.markSitemapForUpdate(this.getOwnedBy(), SiteMapType.NEWS, dbConnection);
}
catch (Exception e) {
logger.error(String.format("Failed to register News Sitemap to be updated for site %d after saving article %d", this.getOwnedBy().getId(), this.getId()),e);
}
}
} finally {
query.close();
//
// If the save worked, commit the changes to the database, otherwise
// perform a rollback.
//
if (saveSucceeded) {
logger.log(Level.DEBUG,
"Commiting article " + this.articleId + " to database.");
//
// even though the connection is autocommiting now theres no harm
// in explicitly committing
//
dbConnection.commit();
} else {
logger.log(Level.DEBUG,
"Could not commit " + this.articleId + " to database. " +
"Performing database rollback.");
//
// even though the connection is autocommiting now theres its
// worth rolling back in the event of an error as the autocommit
// wont kick in.
//
dbConnection.rollback();
}
//
// Clean up the prepared statements
//
query.close();
updateClobQuery.close();
retrieveClobQuery.close();
//
// Enable autocommit again.
//
dbConnection.setAutoCommit(true);
}
return saveSucceeded;
}
/**
* Flushes all page elements and pages that reference this article.
*
* @param dbConnection database connection
* @param logger Logger for reporting
*/
public void flush(Connection dbConnection, Logger logger) {
flush(
new HashSet<Article>(java.util.Arrays.asList(new Article[] {this})),
dbConnection,
logger
);
}
/**
* Flushes all page elements and pages that reference the specified
* set of articles.
*
* Normally the flushing of the articles will be done in its own
* thread so there is no noticeable delay in the use of editorial
* articles tool. Only if the thread pool used for the flushing
* of the articles is disabled, we will do the article flushing
* inline.
*
* @param articles the set or articles to flush.
* @param dbConnection database connection
* @param logger Logger for reporting
*/
public static void flush(Set<Article> articles,
Connection dbConnection,
Logger logger) {
if (articles != null && articles.size() > 0) {
try {
//
// Try to get the thread pool we use for the flushing of the articles.
//
ExecutorService threadPool=ThreadPoolFactory.getInstance().getPool(ThreadPoolFactory.ARTICLE_FLUSHING);
//
// If the thread pool could not be instantiated, maybe it has been
// explicitly disable, do the flushing inline. Otherwise do the
// flushing through one of the thread pool tasks.
//
if (threadPool == null) {
//
// if we are doing the flushing inline, use the connection provided.
//
new ArticleFlushingTask(articles, dbConnection).run();
} else {
//
// Copy the set of received articles to it's own collection,
// so in case the original collection gets modified by
// the caller thread, we don't get affected.
//
articles = new HashSet<Article>(articles);
//
// If the flushing is being done through one of the thread pool
// tasks, don't pass on the provided database connection, let
// the thread create its own. We don't know when the task is
// going to be scheduled to run so it's better we don't hold on
// to the connection.
threadPool.submit(
new ArticleFlushingTask(articles)
);
}
} catch (Exception e) {
StringBuilder error = new StringBuilder("Exception while flushing article ids [ ");
if (articles != null) {
for (Article article:articles) {
if (article != null) {
error.append(article.getId());
}
}
}
error.append("]. Exception:[").append(e);
logger.error(error.toString(),e);
}
}
}
/**
* Get the collection of sites that need to have content flushed if the
* specified article is changed or goes live.
*
* @param article The {@link Article} to get the collection of sites to flush
* for.
*
* @return The collection of {@link Site} instances that will need content
* flushed if the specified article changes.
*/
private static Site[] getArticleFlushingSites(final Article article) {
Site[] flushingSites;
if(article != null) {
final Collection<Site> sites = new HashSet<Site>();
final Category cat = article.getCategory();
// Get any available sites using the article owning site as a syndication
// partner.
if(article.isSyndicated() && (cat != null) && (cat.isUsedInSyndication())) {
sites.addAll(article.getOwnedBy().getPartneringSites());
}
// Make sure we include the owning site, that always needs to be flushed.
sites.add(article.getOwnedBy());
flushingSites = new Site[sites.size()];
flushingSites = sites.toArray(flushingSites);
} else {
flushingSites = null;
}
return flushingSites;
}
/**
* Flush all the page element instances associated to the specified
* list of article instances.
*
* @param articles The list of articles whose associated page element
* instances to flush.
* @param dbConnection Connection to the dabase.
*/
private static void flushAssociatedPageElementInstances(Set<Article> articles, Connection dbConnection) {
try {
if (articles != null && articles.size() > 0) {
//
// HashMap where we will keep the list of all page element
// instances that need to be flushed, classified by site. Through
// this list we will make sure we don't flush the same instances
// over and over again.
//
HashMap<Site,Set<PageElement>> pageElementInstancesToFlush =
new HashMap<Site,Set<PageElement>>();
//
// Iterate through each of the articles and getting all the
// page element instances to flush and adding them to the map.
//
for (Article article: articles) {
if (article != null) {
try {
// If this is article is available for all sites, flush the associated
// page element instances for all sites where this article might appear.
// Otherwise it only needs to be flushed for the site to which the
// article belongs.
//
Site[] sites = null;
if (article.generalArticle) {
sites = Site.getSites(dbConnection, logger);
} else if(article.isSyndicated() && article.getCategory().isUsedInSyndication()) {
sites = getArticleFlushingSites(article);
} else {
sites = new Site[] {article.ownedBy};
}
for (int ii = 0; ii < sites.length; ++ii) {
//
// Ensure the index is flushed for the live site version
//
Site flushSite = Site.getInstance(sites[ii].getId());
//############################
// ARTICLE INDEX ELEMENTS.
//############################
ArticleIndex[] articleIndexes = ArticleIndex.getArticleIndexesForFlushing(
flushSite,
article.category,
article.homePageStatusChanged || article.onHomePage,
dbConnection,
logger
);
if (null != articleIndexes && articleIndexes.length > 0) {
Set<PageElement> pageElementsSet = pageElementInstancesToFlush.get(flushSite);
if (pageElementsSet == null) {
pageElementInstancesToFlush.put(flushSite, new HashSet<PageElement>());
pageElementsSet = pageElementInstancesToFlush.get(flushSite);
}
pageElementsSet.addAll(
new HashSet<PageElement>(java.util.Arrays.asList(articleIndexes))
);
}
//#######################################
// MULTICATEGORY ARTICLE INDEX ELEMENTS.
//#######################################
MulticategoryArticleIndex[] multiArticleIndexes =
MulticategoryArticleIndex.getMulticategoryArticleIndexesForArticle(
article.category,
flushSite,
article.isSyndicated() ? article.getOwnedBy() : null,
dbConnection,
logger
);
if (multiArticleIndexes != null) {
Set<PageElement> pageElementsSet = pageElementInstancesToFlush.get(flushSite);
if (pageElementsSet == null) {
pageElementInstancesToFlush.put(flushSite, new HashSet<PageElement>());
pageElementsSet = pageElementInstancesToFlush.get(flushSite);
}
pageElementsSet.addAll(
new HashSet<PageElement>(java.util.Arrays.asList(multiArticleIndexes))
);
}
//###############################
// CUSTOM ARTICLE INDEX ELEMENTS.
//###############################
CustomArticleIndex[] customIndexes =
CustomArticleIndex.getCustomArticleIndexesForFlushing(
flushSite,
article.category,
article.homePageStatusChanged || article.onHomePage,
dbConnection,
logger
);
if (null != customIndexes && customIndexes.length > 0) {
Set<PageElement> pageElementsSet = pageElementInstancesToFlush.get(flushSite);
if (pageElementsSet == null) {
pageElementInstancesToFlush.put(flushSite, new HashSet<PageElement>());
pageElementsSet = pageElementInstancesToFlush.get(flushSite);
}
pageElementsSet.addAll(
new HashSet<PageElement>(java.util.Arrays.asList(customIndexes))
);
}
//#######################################
// TABBED VIDEO ARTICLE INDEX ELEMENTS.
//#######################################
TabbedVideoArticleIndex[] tabbedIndexes =
TabbedVideoArticleIndex.getTabbedVideoArticleIndexes(
flushSite,
article.category,
false,
dbConnection,
logger
);
if (null != tabbedIndexes && tabbedIndexes.length > 0) {
Set<PageElement> pageElementsSet = pageElementInstancesToFlush.get(flushSite);
if (pageElementsSet == null) {
pageElementInstancesToFlush.put(flushSite, new HashSet<PageElement>());
pageElementsSet = pageElementInstancesToFlush.get(flushSite);
}
pageElementsSet.addAll(
new HashSet<PageElement>(java.util.Arrays.asList(tabbedIndexes))
);
}
//############################
// ARTICLE PACKAGES
//############################
// Get the list of article packages that include
// this article.
//
ArticlePackage[] packages = ArticlePackage.getArticlePackagesForArticleId(
article.articleId, null, dbConnection, logger
);
//
// For each of the article packages, get the slots
// that include them.
//
if (packages != null) {
for (int i=0; i<packages.length; i++) {
ArticlePackageSlot[] slots = ArticlePackageSlot.getArticlePackagesSlotsForPackageId(
packages[i].getId(),
dbConnection,
logger
);
//
// For each slot, get the custom article indexes associated
// and add them to the list of page elements to flush.
//
if (slots != null) {
customIndexes = CustomArticleIndex.getCustomArticleIndexes(
slots,
dbConnection,
logger
);
if (null != customIndexes && customIndexes.length > 0) {
for (PageElement element:customIndexes) {
Set<PageElement> pageElementsSet = pageElementInstancesToFlush.get(element.getSite());
if (pageElementsSet == null) {
pageElementInstancesToFlush.put(element.getSite(), new HashSet<PageElement>());
pageElementsSet = pageElementInstancesToFlush.get(flushSite);
}
pageElementsSet.add(element);
}
}
}
}
}
//############################
// LIVE MATCH CONTENT
//############################
//
// If any of the linked objects is a match,
// flush the LiveMatchContent instances for the same site as Article.
//
//
if (article.linkedObjects != null) {
List<DetailObject> linkedObjectsList = article.linkedObjects.get(DetailType.MATCH_KEY);
if (!ListUtils.isNullOrEmpty(linkedObjectsList)) {
for (DetailObject detailObj : linkedObjectsList) {
Match match = (Match) detailObj;
if(match != null) {
LiveMatchContent[] liveMatchContents = LiveMatchContent.getLiveMatchContent(flushSite, dbConnection, logger);
if (liveMatchContents != null) {
Set<PageElement> pageElementsSet = pageElementInstancesToFlush.get(flushSite);
if (pageElementsSet == null) {
pageElementInstancesToFlush.put(flushSite, new HashSet<PageElement>());
pageElementsSet = pageElementInstancesToFlush.get(flushSite);
}
pageElementsSet.addAll(
new HashSet<PageElement>(java.util.Arrays.asList(liveMatchContents))
);
}
}
}
}
}
//###############################
// CUSTOM LINKED ARTICLE DETAIL
//###############################
//
//
// If the article has linked objects, flush all the custom linked
// article detail elements that are configured to link to the page
// of this article and to the detail type id of the linked objects.
//
if (article.linkedObjects != null &&
article.linkedObjects.size() > 0 &&
article.page != null &&
article.ownedBy != null) {
//
// Get the detail type ids of the linked objects.
//
int count = 0;
int[] detailTypeIds = new int[article.linkedObjects.size()];
Iterator<String> linkedObjectIter = article.linkedObjects.keySet().iterator();
while (linkedObjectIter.hasNext()) {
List<DetailObject> detailObjects = article.linkedObjects.get(linkedObjectIter.next());
if (!ListUtils.isNullOrEmpty(detailObjects)) {
DetailObject detailObject = detailObjects.get(0);
detailTypeIds[count++] = detailObject.getDetailTypeId();
}
}
//
// Get the list of instances of CustomLinkedArticleDetail configured
// for any of the detail type ids retrieved and that will pull
// articles from the same page to which this article belongs.
//
CustomLinkedArticleDetail[] customLinkedArticleDetails =
CustomLinkedArticleDetail.getCustomLinkedArticleDetailsForDetailTypeIds(
detailTypeIds,
article.page.getId(),
false,
flushSite,
dbConnection,
logger
);
if (customLinkedArticleDetails != null) {
Set<PageElement> pageElementsSet = pageElementInstancesToFlush.get(flushSite);
if (pageElementsSet == null) {
pageElementInstancesToFlush.put(flushSite, new HashSet<PageElement>());
pageElementsSet = pageElementInstancesToFlush.get(flushSite);
}
pageElementsSet.addAll(
new HashSet<PageElement>(Arrays.asList(customLinkedArticleDetails))
);
}
}
}
} catch (Exception e) {
logger.error("Exception flushing page element instances for article id [" + article.getId() + "].",e);
}
}
}
//
// Now flush all the found page element instances.
//
HtmlCache htmlCache = HtmlCache.getInstance();
for (Site site:pageElementInstancesToFlush.keySet()) {
htmlCache.decachePageElementsHtml(
pageElementInstancesToFlush.get(site),
HtmlCache.REMOVE_ALL, // remove for all detail ids.
null, // remove for all pages.
site,
dbConnection,
logger
);
}
}
} catch (Exception e) {
StringBuilder error = new StringBuilder("Exception while flushing page element instances for article ids [ ");
for (Article article:articles) {
if (article != null) {
error.append(article.getId());
}
}
error.append("]. Exception:[").append(e);
logger.error(error.toString(),e);
}
}
/**
* Get the collection of paths that need to be flushed for an article on a
* particular site.
*
* @param article The {@link Article} to get the paths for.
* @param articleSite The {@link Site} to get the article paths for.
* @param dbConnection The {@link Connection} to use.
*
* @return The {@link Collection} of paths that should be flushed.
*
* @throws SQLException If there was a problem fetching the site pages.
*/
private static Collection<String> getAssociatedPathsForSite(
final Article article,
final Site articleSite,
final Connection dbConnection)
throws SQLException {
final Set<String> articlePaths = new HashSet<String>();
//
// Flush the detail page where this article appears
//
final PageReference pageRef = article.getPageForSite(articleSite, dbConnection, LOGGER);
Curl curl = (pageRef != null) ? pageRef.getCurl() : null;
if (curl != null) {
articlePaths.add(curl.getFlushingPath());
}
//
// The default behaviour for an article detail page when no article id is
// specified is to pick up the most recent (or highest ranked article).
// This may be displaying the current article, although there's no way to
// determine this for certain. We flush it regardless. In this case it's
// better to flush slightly too much than too little, since it's a case
// that does occur on the sites and there's no other way to account for
// it.
//
if ((pageRef != null) && (pageRef.getPage() != null)) {
curl = pageRef.getPage().getCurl();
if (curl != null) {
articlePaths.add(curl.toString());
}
}
//
// Flush all pages which use the same category - this accounts for
// examples such as articles on the fixture list page, which is not an
// article detail page.
//
Page[] pages = Page.getPages(article.category,
articleSite,
dbConnection,
logger);
if (null != pages) {
for (int ii = 0; ii < pages.length; ++ii) {
curl = pages[ii].getCurl();
if (null != curl) {
articlePaths.add(curl.toString());
}
}
}
//
// If this is a desktop news alert article, flush the news feed for
// this site, and the general news feed for all sites. This flush
// happens if either the article is available for alerts, or it's
// alert status has changed (indicating that it has been removed
// from the alert).
//
if (article.alertStatusChanged || article.availableForAlerts) {
articlePaths.add(RssController.getNewsAlertPath(articleSite));
}
//
// Now that we have rssable page elements we also need to flush
// the rss path for the article detail for this site.
//
articlePaths.add(RssController.getRssArticleDetailPath(articleSite, article.articleId));
//
// If this site is a podcaster then we must always flush the generic
// podcasting url. We cant just only flush the podcasting articles because
// this would mean that when one is unchecked as a podcasting article
// then the page would not flush.
//
if(articleSite.isPodCaster()) {
articlePaths.add(RssController.getPodcastPath(articleSite));
}
//
// Also flush the mobile page containing this article, if needed
// e.g.
// /mobile/articleDetail/0,,10282~1194602,00.xml for a normal article
// /mobile/matchReportDetail/0,,10282~43245,00.xml for a match report
//
if (article.isMobileArticle()) {
articlePaths.add("/mobile/articleDetail/0,," + articleSite.getId() + "~" + article.articleId + ",00.xml");
if (article.category != null && article.category.getCategoryId() == Category.POST_MATCH_ANALYSIS) {
List<DetailObject> linkedObjectsList = article.linkedObjects.get(DetailType.PLAYER_KEY);
if (!ListUtils.isNullOrEmpty(linkedObjectsList)) {
for (DetailObject detailObj : linkedObjectsList) {
Match match = (Match) detailObj;
if (match != null) {
articlePaths.add("/mobile/matchReportDetail/0,," + articleSite.getId() + "~" + match.getId() + ",00.xml");
}
}
}
}
}
return articlePaths;
}
/**
* Flush all the page paths associated to the specified
* list of article instances.
*
* @param articles The list of articles whose associated paths to flush.
* @param dbConnection Connection to the dabase.
*/
private static void flushAssociatedPaths(Set<Article> articles, Connection dbConnection) {
try {
if (articles != null && articles.size() > 0) {
//
// Set where we will keep the list of all paths that need to be flushed.
//
final Set<String> pathsToFlush = new HashSet<String>();
//
// Iterate through each of the articles and getting all the
// paths to flush and adding them to the set.
//
for (Article article: articles) {
if (article != null && (article.isLive() || article.isExpired())) {
try {
final Collection<Site> articleSites = new HashSet<Site>();
// Get all the sites that need flushings
articleSites.add(article.ownedBy);
articleSites.addAll(article.getOwnedBy().getPartneringSites());
for(Site articleSite: articleSites) {
pathsToFlush.addAll(getAssociatedPathsForSite(article, articleSite, dbConnection));
}
} catch (Exception e) {
logger.error("Exception flushing article paths for article id [" + article.getId() + "].", e);
}
}
}
//
// Send the set of paths to the file flusher. This uses jms to
// pass the list of paths to all the cache boxes.
//
if (pathsToFlush.size() > 0) {
FileFlusher.sendFlushRequest(pathsToFlush.toArray(new String[pathsToFlush.size()]));
}
}
} catch (Exception e) {
StringBuilder error = new StringBuilder("Exception while flushing paths for article ids [ ");
for (Article article:articles) {
if (article != null) {
error.append(article.getId());
}
}
error.append("]. Exception:[").append(e);
logger.error(error.toString(),e);
}
}
/**
* Flush all the detail object instances linked to the specified
* list of article instances.
*
* @param articles The list of articles whose associated page element
* instances to flush.
* @param dbConnection Connection to the dabase.
*/
private static void flushAssociatedVideoTypes(Set<Article> articles, Connection dbConnection) {
try {
if (articles != null && articles.size() > 0) {
//
// HashMap where we will keep the list of all video types
// instances that need to be flushed, classified by article category.
// Through this list we will make sure we don't flush the same instances
// over and over again.
//
HashMap<Category,Set<VideoType>> videoTypesToFlush =
new HashMap<Category,Set<VideoType>>();
//
// Iterate through each of the articles getting all the
// video type instances to flush and adding them to the map.
//
for (Article article: articles) {
if ((article != null) && (article.isLive() || article.isExpired()) && !ArrayUtils.isNullOrEmpty(article.linkedVideos)) {
try {
final Collection<Site> articleSites = new HashSet<Site>();
// Get all the sites that need flushings
articleSites.add(article.ownedBy);
articleSites.addAll(article.getOwnedBy().getPartneringSites());
for(Site articleSite: articleSites) {
if(articleSite.isArticleVideoLinksEnabled()) {
Collection<VideoType> videoTypes = VideoType.getVideoTypes(articleSite);
if ((videoTypes != null) && !videoTypes.isEmpty()) {
Set<VideoType> videoTypesSet = videoTypesToFlush.get(article.getCategory());
if (videoTypesSet == null) {
videoTypesToFlush.put(article.getCategory(), new HashSet<VideoType>());
videoTypesSet = videoTypesToFlush.get(article.getCategory());
}
videoTypesSet.addAll(videoTypes);
}
}
}
} catch (Exception e) {
logger.error("Exception flushing Video Types for article id [" + article.getId() + "].",e);
}
}
}
//
// Flush all the found video types.
//
for (Category category:videoTypesToFlush.keySet()) {
for (VideoType videoType:videoTypesToFlush.get(category)) {
SvaCachingFilter.jmsFlushOfVideoTypeIdAndRequestTypes(
videoType.getId(),
""+SvaXmlHttpServlet.SEARCH_CLIPS_BY_ARTICLE_CATEGORY,
(category != null ? ""+ category.getCategoryId() : ""));
}
}
}
} catch (Exception e) {
StringBuilder error = new StringBuilder("Could not flush sva xml for article ids [ ");
for (Article article:articles) {
if (article != null) {
error.append(article.getId());
}
}
error.append("]. Exception:[").append(e);
logger.error(error.toString(),e);
}
}
/**
* Tests this Article for equality with another object.
* <p>
* If the given object is not an Article then this method immediately returns
* false.
* <p>
* For two Articles to be considered equal, we simply test whether their IDs
* are equal.
* <p>
* This method satisfies the general contract of the {@link
* java.lang.Object#equals(Object) Object.equals} method. </p>
*
* @param objectToCompare The object to which this object is to be compared
* @return <tt>true</tt> if, and only if, the given object is an Article that
* is identical to this Curl
*/
public boolean equals(Object objectToCompare) {
boolean isEqual;
if (objectToCompare == this) {
//
// Object is always equal to itself!
//
isEqual = true;
} else if (!(objectToCompare instanceof Article)) {
//
// Objects of different types are never equal
//
isEqual = false;
} else {
//
// Articles are equal if their IDs are equal
//
isEqual = (this.articleId == ((Article) objectToCompare).articleId);
}
return isEqual;
}
/**
* If the article has an id (it's been saved), then the id is returned,
* otherwise the Object hashCode() is called. This means that two articles
* with the same id will hash to the same value.
*
* @returns the hashcode for the article
*/
public int hashCode() {
int code;
if (this.articleId == NEW_ARTICLE) {
code = super.hashCode();
} else {
code = this.articleId;
}
return code;
}
/**
* Deletes an article from the database, removing it's ranking and article
* parts.
*
* @param dbConnection Database connection.
* @param logger Logger
*
* @throws SQLEXception On database error
*/
public void delete(Connection dbConnection, Logger logger)
throws SQLException,
URISyntaxException,
InvalidCurlException {
//
// Articles that have not been saved to the database cannot be deleted.
//
if (NEW_ARTICLE != this.articleId) {
logger.log(Level.DEBUG,
"Deleting article " + this.articleId + " from database.");
//
// Note by GFE Jun 2005
// We've had some subtle problems with the editorial_articles , ranks and
// article_keywords tables getting conflicting locks and deadlocks
// on them. Diagnosing exactly what is wrong is tricky.
// Supposition is that previously we used to keep autocommit off
// and therefore hold locks on rows in the 3 tables for long periods.
// Referntial integrity between article_keywords ranks and
// editorial_articles means that is process A on STG05 updates articleid 3
// in a long uncommitted chain than if someone does the same save on
// articleId 3 on stg06 then you start to get into knots.
// The process is unclear to me still, even though i've managed
// to reproduce it on dev by making the ranks and article_keywords tables use
// their own uncommitting DB connections to do their updates (this causes
// the exact same locking errors on dev immediately).
// As a solution / workaround I've elected to turn autommit on for
// the main connection on the understanding that this dramitically reduces
// the timeframe for conficts to occur. I don't know for sure that this
// will work but having created a similar errors on dev by using 3
// seperate DBConnections (one for each table) turning on automcommit
// stopped the errors - so its worth a shot.
//
dbConnection.setAutoCommit(true);
boolean deleteSucceeded = false;
PreparedStatement query = ConnectionPool.prepareStatement(
"DELETE FROM article_keywords " +
"WHERE artl_id = ?",
dbConnection,
logger
);
try {
//
// Delete the article keywords
//
query.setInt(1, this.articleId);
ConnectionPool.executeUpdate(query, dbConnection, logger);
query.close();
//
// If the article has been ranked previously, remove it's ranks
//
this.setRank(CATEGORY_RANK_TYPE,
NOT_RANKED,
this.category,
dbConnection,
logger);
this.setRank(HOME_CATEGORY_RANK_TYPE,
NOT_RANKED,
null,
dbConnection,
logger);
//
// Delete any linked images associated with the article
//
query = ConnectionPool.prepareStatement(
"DELETE FROM article_images " +
"WHERE article_id = ? ",
dbConnection,
logger
);
query.setInt(1, this.articleId);
ConnectionPool.executeUpdate(query, dbConnection, logger);
query.close();
//
// Delete linked videos
//
query = ConnectionPool.prepareStatement(
"DELETE FROM article_videos " +
"WHERE article_id = ? ",
dbConnection,
logger
);
query.setInt(1, this.articleId);
ConnectionPool.executeUpdate(query, dbConnection, logger);
query.close();
//
// Delete any linked objects associated with the article
//
query = ConnectionPool.prepareStatement(
"DELETE FROM article_links " +
"WHERE artl_id = ? ",
dbConnection,
logger
);
query.setInt(1, this.articleId);
ConnectionPool.executeUpdate(query, dbConnection, logger);
query.close();
//
// Delete the article
//
query = ConnectionPool.prepareStatement(
"DELETE FROM editorial_articles " +
"WHERE artl_id = ?",
dbConnection,
logger
);
query.setInt(1, this.articleId);
ConnectionPool.executeUpdate(query, dbConnection, logger);
//
// Flush the pages which reference this article
//
this.flush(dbConnection,logger);
deleteSucceeded = true;
//
// Now, remove the article from the lucene indexing
//
LuceneUtils.markForLuceneIndexing(
this.ownedBy.getId(),
this.getDetailTypeId(),
this.articleId,
true, // delete the article from the index
false,
dbConnection,
logger
);
} finally {
if (deleteSucceeded) {
logger.log(Level.DEBUG,
"Deleted article " + this.articleId + " from database.");
//
// Commit the changes to the database. This should already have
// happened as autocommit is on but there no harm in this
//
dbConnection.commit();
} else {
logger.log(Level.WARN,
"Could not delete article " + this.articleId +
" from database. Rollback performed.");
//
// Deletion failed, perform a rollback; This should be Ok too
// as if the query fails no commit will have been done - even
// with autocommit on
//
dbConnection.rollback();
}
query.close();
}
}
}
/**
* Updates the ranking data for this article.
*
* @param rankType A string used in the database to identify the type of
* rank - typically this is either "TOP5" for page ranks
* or "HMPG" for home page ranks
* @param rank The current rank of this article. 1 indicates that this
* article is ranked first
* @param priorRank The previous rank of this article. If this is the same
* as the rank it means that the rank has not changed and
* no action needs to be taken.
* @param category Article page ranking effectively applies to only
* a single category, whereas home page ranking applies
* across a swath of categories. This parameter is used to
* restrict the updates below to apply to only articles in
* the same category this parameter.
* @param dbConnection Database Connection
* @param logger Logger
*
* @throws SQLException on database error.
*/
private void setRank(String rankType,
int rank,
Category category,
Connection dbConnection,
Logger logger) throws SQLException {
PreparedStatement query = null;
try {
//
// Updates only required if this articles rank has changed.
//
logger.debug("Updating rank " + rankType + " to " + rank +
" for article " + this.articleId);
//
// Synchronise on the site - minimise the risk of multiple webmasters
// for a single site overriding each others work - this has been a
// problem in the past and is worth catering for. This is not a complete
// solution since requests may still be load balanced across different
// servers, in which case concurrency issues still exist.
//
synchronized (this.usedBy) {
//
// Get the existing ranks from the database, in order, excluding the
// current article - this is either being added or altered and it's
// current position is irrelevent.
//
query = ConnectionPool.prepareStatement(
"SELECT artl_id, catg_id " +
"FROM ranks " +
"WHERE rnkt_cd = ? " +
"AND orgn_id = ? " +
"AND artl_id <> ? " +
(null != category ? "AND catg_id = ? " : "") +
"ORDER BY seq_no ASC ",
ResultSet.TYPE_SCROLL_INSENSITIVE,
ResultSet.CONCUR_READ_ONLY,
dbConnection,
logger
);
query.setString(1, rankType);
query.setInt(2, this.usedBy.getId());
query.setInt(3, this.articleId);
if (null != category) {
query.setInt(4, category.getCategoryId());
}
ResultSet rs = ConnectionPool.executeQuery(query, dbConnection, logger);
//
// Arrays to hold the ranked article ids and their categories in order.
//
int[] articleIds = null;
int[] categoryIds = null;
if (rs.last()) {
//
// If this article is being ranked, ensure the array is large enough
// to cope (the query deliberately excluded the current article
// from the results).
//
int length = rs.getRow() + (rank != NOT_RANKED ? 1 : 0);
articleIds = new int[length];
categoryIds = new int[length];
rs.beforeFirst();
//
// Build an array list containing the articles in sequence order.
// If the current article is ranked, an empty slot is added to the
// array.
//
int position;
for (int ii = 0; rs.next(); ++ii) {
//
// Create an empty slot in the array when the correct rank position
// when this article is reached,
//
position = ii + (rank != NOT_RANKED && ii >= rank - 1 ? 1 : 0);
articleIds[position] = rs.getInt("artl_id");
categoryIds[position] = rs.getInt("catg_id");
}
//
// Add the current article in the empty slot created above, ensuring
// we don't totter off the edge of the array if the rank value is
// set too high.
//
if (rank != NOT_RANKED) {
position =
(rank > articleIds.length ? articleIds.length : rank) - 1;
articleIds[position] = this.articleId;
categoryIds[position] = this.category.getCategoryId();
}
} else if (rank != NOT_RANKED) {
//
// Cope with the situation where there are no existing ranked
// articles.
//
articleIds = new int[] {this.articleId};
categoryIds = new int[] {this.category.getCategoryId()};
}
rs.close();
query.close();
//
// Delete the existing ranks
//
query = ConnectionPool.prepareStatement(
"DELETE FROM ranks " +
"WHERE rnkt_cd = ? " +
"AND orgn_id = ? " +
(null != category ? "AND catg_id = ? " : ""),
dbConnection,
logger
);
query.setString(1, rankType);
query.setInt(2, this.usedBy.getId());
if (null != category) {
query.setInt(3, category.getCategoryId());
}
ConnectionPool.executeUpdate(query, dbConnection, logger);
query.close();
//
// Insert the updated ranks back into the database.
//
if (null != articleIds) {
query = ConnectionPool.prepareStatement(
"INSERT INTO ranks (" +
" rnkt_cd, " +
" orgn_id, " +
" artl_id, " +
" catg_id, " +
" seq_no " +
") VALUES (?, ?, ?, ?, ?)",
dbConnection,
logger
);
//
// Reinsert the new ranks, note that only a certain number of articles
// may be ranked - any articles over the maximum are not reinserted
// and their ranks are lost.
//
for (int ii = 0;
ii < articleIds.length && ii < MAXIMUM_RANKED_ARTICLES;
++ii) {
query.setString(1, rankType);
query.setInt(2, this.usedBy.getId());
query.setInt(3, articleIds[ii]);
query.setInt(4, categoryIds[ii]);
query.setInt(5, ii + 1);
logger.debug("Inserting rank " + rankType + " for site " +
this.usedBy.getId() + ", article id is " +
" " + articleIds[ii] + ", and seq no = " + (ii+1));
ConnectionPool.executeUpdate(query, dbConnection, logger);
}
}
}
} finally {
if (null != query) {
query.close();
}
}
}
/**
* Returns an array of articles waiting for syndication for the required set
* of article categories. An article must not be syndicated until 15 minutes
* after the article has been posted on a website to give preferrential
* treatment to the site's website.
*
* Note that in order to improve performance, articles greater than 14 days
* old are excluded. This is not an issue, since the window of processing
* is measured in minutes rather than days.
*
* @param syndicationType 1 means syndicate article
* 2 means withdraw previously syndicated article
* 3 means correct previously syndicated article
* this is used to modify the query slightly to retrieve the
* rows appropriate for the action
* @param minutesToDelay Minutes to lag behind article launch before
* syndicating (from config)
* @param minutesToSearchBack Minutes we look back in the query for
* articles for any type of syndication. There's
* no point wading through all articles - just
* do last few days worth (from config)
* @param minutesForCorrection Minutes we wait before an article is picked up
* for a correction (number of minutes article
* last update date has to be after article
* syndicated date)
* @param lockId Lock Id used to locka row on a database
* @param connection DB connection to use for SQL query.
* @param logger sink for log messages
* @return an array of articles for the required set of article categories.
*/
public static Article[] getArticlesPendingSyndication(int syndicationType,
int minutesToDelay,
int minutesToSearchBack,
int minutesForCorrection,
int lockId,
Connection connection,
Logger logger)
throws SQLException {
Article[] articles = null;
PreparedStatement query = null;
//
// For all sites that support syndication (video_syndication_supported='Y)
// get all articles that are marked for syndication, i.e.
// syndicated_flg = 'Y' and the syndication_timestamp has not yet been set
// (indicates that the article has not yet been processed for syndication)
// and the article is live on site and at least 15 mins old.
//
// First, we lock the row on a database.
//
String theQuery =
"UPDATE editorial_articles ea " +
"SET lock_id = ?, " +
"lock_date = sysdate " +
"WHERE orgn_club_id IN ( " +
"SELECT orgn_id " +
"FROM organisations " +
"WHERE video_syndication_supported = 'Y' " +
") AND article_date > SYSDATE - 7 " +
"AND ea.catg_id IN " +
"(SELECT DISTINCT catg_id FROM partner_supported_categories) " +
(syndicationType == EditorialSyndicator.SYNDICATE_ARTICLE ?
//
// If we are syndicating then insist articles have:
// * not already syndicated already (not locked or previously timestamped)
// * not been removed from the site yet
//
" AND lock_id is NULL" +
" AND syndication_timestamp IS NULL" +
" AND (site_removed_date IS NULL OR " +
" site_removed_date > SYSDATE - ?/1440 )"
: syndicationType == EditorialSyndicator.WITHDRAW_ARTICLE ?
//
// If we are withdrawing from syndicating then insist articles have:
// * already been syndicated (with a timestamp)
// * not already been unsyndicated
// * been removed from the site recently
//
" AND lock_id is NULL" +
" AND syndication_timestamp IS NOT NULL " +
" AND unsyndicated_date is null " +
" AND site_removed_date > SYSDATE - ?/1440 "
: syndicationType == EditorialSyndicator.CORRECT_ARTICLE ?
//
// If we're correcting then insist articles have:
// * already been syndicated ( with a timestamp)
// * not already been unsyndicated
// * not been removed from the site
// * edited more than 2 minutes after they were last syndicated
// (Note that last_edited_date is set on a trigger by any update
// of articles)
//
" AND lock_id is NULL" +
" AND syndication_timestamp IS NOT NULL " +
" AND unsyndicated_date is null " +
" AND (site_removed_date IS NULL OR " +
" site_removed_date > SYSDATE - ?/1440 )" +
" AND last_edit_date > syndication_timestamp + ?/1440 "
:
""
) +
" AND site_posted_date IS NOT NULL" +
" AND site_posted_date < SYSDATE - ?/1440" +
" AND article_date <= SYSDATE - ?/1440 " +
" AND last_edit_date > sysdate - (?/1440)" +
" AND lock_date is NULL" +
" AND syndicated_flg ='Y'";
query = ConnectionPool.prepareStatement(
theQuery,
connection,
logger
);
try {
int parameter = 0;
query.setInt(++parameter, lockId);
//
// Depending on the syndication type - we need to add various params
//
if (syndicationType == EditorialSyndicator.SYNDICATE_ARTICLE) {
query.setInt(++parameter,minutesToDelay );
}
if (syndicationType == EditorialSyndicator.WITHDRAW_ARTICLE) {
query.setInt(++parameter,minutesToSearchBack );
}
if (syndicationType == EditorialSyndicator.CORRECT_ARTICLE) {
query.setInt(++parameter,minutesToDelay );
query.setInt(++parameter,minutesForCorrection );
}
query.setInt(++parameter,minutesToDelay );
query.setInt(++parameter,minutesToDelay );
query.setInt(++parameter,minutesToSearchBack );
int rowsUpdated = ConnectionPool.executeUpdate(query,
connection,
logger);
logger.log(Level.DEBUG,"Updated Article rows: " + rowsUpdated);
if (rowsUpdated > 0) {
query.close();
//
// The following query could just get items locked by the query above
// but since theres no index on lock_id its much faster to
// limit down by site again - as these are indexed.
// NB these criteria need to be kept in step with the criteria
// in the update query above otherwise you wont pick up all the rows
// you locked
// Ideally we should put an index on the lock_id - but DBAs disagree
//
query = ConnectionPool.prepareStatement(
"SELECT " + Article.SQL_ARTICLE_COLUMNS +
"FROM editorial_articles a " +
"WHERE a.orgn_club_id IN ( " +
"SELECT orgn_id " +
"FROM organisations " +
"WHERE video_syndication_supported = 'Y' " +
") AND article_date > SYSDATE - 7 " +
"AND a.catg_id IN " +
"(SELECT DISTINCT catg_id FROM partner_supported_categories) " +
"AND a.lock_id = ? ",
connection,
logger
);
query.setInt(1, lockId);
//
// The second parameter determines if extra article information (image
// match, player, rally, crew, team) are retrieved for the articles.
//
articles = Article.getArticles(query,
true,
null,
false,
connection,
logger);
}
} finally {
if (null != query) {
query.close();
}
}
return articles;
}
/**
* Replace relative url's in the html with fully qualified urls.
* I.e. will replace <img src="/foo.html"> with
* <img src="http://www.sitedomain.co.uk/foo.html">
* and <a href="/page/foo.html> with
* <a href="http://www.sitedomain.co.uk/page/foo.html>
*
* @param html A string representing some html
*
* @return A string with all relative url's replaced with fully qualified urls
*/
private String fixRelativeUrls(String html) {
return RELATIVE_URL_PATTERN.matcher(html).replaceAll(
"$1" + this.getOwnedBy().getWwwDomain() + "/$5"
);
}
/**
* A method that creates an XML document containing all information about
* this article.
*
* This builds an XML file that looks like this:
*
* <?xml version="1.0" encoding="UTF-8" ?>
* <article>
* <articleId>630459</articleId>
* <syndicationTimeStamp>2005-07-26T05:52:35</syndicationTimeStamp>
* <unSyndicationTimeStamp>2005-07-26T05:52:35</unSyndicationTimeStamp>
* <site>Middlesbrough</site>
* <copyright>dev.mfc.premiumtv.co.uk ltd</copyright>
* <category>News</category>
* <headline>THIS IS JUST A TEST</headline>
* <articleBody>
* <![CDATA[ <p>this is just a test 26th july</p>]]>
* </articleBody>
* </article>
*
* @param dbConnection Database connection
* @param logger Error logger
*
* @return Returns a document that contains XML for this article. Null is
* returned if there were any problems creating XML for this article.
*/
public Document buildXmlDoc(Connection dbConnection,
Logger logger) {
Document document = null;
try {
//
// First check that article body contains some text.
//
String articleBody = HTMLProcessor.cleanHtml(this.getBody());
String articleHeader = HTMLProcessor.cleanHtml(this.getHeadline());
if (null != articleBody && !"".equals(articleBody)) {
DocumentBuilderFactory domFactory = DocumentBuilderFactory.newInstance();
DocumentBuilder domBuilder = domFactory.newDocumentBuilder();
document = domBuilder.newDocument();
Element rootElement = document.createElement("article");
document.appendChild(rootElement);
Element childElement;
//
// build the Dom Document containing all article data.
// First create a root element - article.
//
Element element = document.createElement("articleId");
element.appendChild(
document.createTextNode((new Integer(this.articleId).toString()))
);
rootElement.appendChild(element);
element = document.createElement("syndicationTimeStamp");
element.appendChild(
document.createTextNode(this.getSyndicatedXSDateTime())
);
rootElement.appendChild(element);
element = document.createElement("unSyndicationTimeStamp");
element.appendChild(
document.createTextNode(this.getSyndicatedXSDateTime())
);
rootElement.appendChild(element);
element = document.createElement("syndicationDate");
element.appendChild(
document.createTextNode(
new SimpleDateFormat("yyyyMMdd").format(new java.util.Date())
)
);
rootElement.appendChild(element);
element = document.createElement("syndicationTime");
element.appendChild(
document.createTextNode(
new SimpleDateFormat("HH:mm").format(new java.util.Date())
)
);
rootElement.appendChild(element);
element = document.createElement("site");
element.appendChild(
document.createTextNode(this.getOwnedBy().getCommonName())
);
rootElement.appendChild(element);
element = document.createElement("siteUrl");
element.appendChild(
document.createTextNode(this.getOwnedBy().getWwwDomain())
);
rootElement.appendChild(element);
element = document.createElement("siteShortName");
element.appendChild(
document.createTextNode(this.getOwnedBy().getShortName())
);
rootElement.appendChild(element);
element = document.createElement("copyright");
element.appendChild(
document.createTextNode(this.getOwnedBy().getCopyrightNote())
);
rootElement.appendChild(element);
element = document.createElement("categoryId");
element.appendChild(document.createTextNode(
(new Integer(this.category.getCategoryId()).toString())
));
rootElement.appendChild(element);
element = document.createElement("category");
element.appendChild(
document.createTextNode(this.category.getDescription())
);
rootElement.appendChild(element);
if (null != this.videoHi) {
element = document.createElement("videoHi");
element.appendChild(
document.createTextNode(this.videoHi)
);
rootElement.appendChild(element);
}
if (null != this.videoLo) {
element = document.createElement("videoLo");
element.appendChild(
document.createTextNode(this.videoLo)
);
rootElement.appendChild(element);
}
try {
String[] keywords = this.getRelatedArticleKeywords(dbConnection, logger);
String keyword = "";
if (null != keywords && keywords.length > 0) {
keyword = keywords[0];
}
element = document.createElement("keyword");
element.appendChild(
document.createTextNode(keyword)
);
rootElement.appendChild(element);
} catch (SQLException sqlEx) {
logger.log(Level.DEBUG, "Unable to fetch related article keywords for articleId " + this.articleId);
}
//
// Format headline. First replace all non ASCII characters - this method
// actually adds <p> tags, so afterwards go through and strip all HMTL
// tags. But, first, as we escape '&' in headline with '&amp;' and after
// running this through XML Document Builder, we have double escaped
// '&', we revert escaping here. And also, replace all '&nbsp;' with
// just an empty space.. There really is no need for this sort of thing
// in title.
//
String headline = HTMLProcessor.removeHtmlTags(this.headline);
headline = headline.replaceAll("&amp;", "&");
headline = headline.replaceAll("&nbsp;", " ");
element = document.createElement("headline");
element.appendChild(document.createTextNode(
HTMLProcessor.removeAllHtmlTags(
HTMLProcessor.replaceNonASCII(headline).toUpperCase()
)
));
rootElement.appendChild(element);
element = document.createElement("headlineWithHtml");
element.appendChild(document.createCDATASection(this.headline));
rootElement.appendChild(element);
if (null != this.teaser && this.teaser.length() > 0) {
String teaser = HTMLProcessor.removeHtmlTags(this.teaser);
element = document.createElement("teaser");
element.appendChild(document.createCDATASection(teaser));
rootElement.appendChild(element);
element = document.createElement("teaserWithHtml");
element.appendChild(document.createCDATASection(this.teaser));
rootElement.appendChild(element);
}
element = document.createElement("articleBody");
element.appendChild(document.createCDATASection(articleBody));
rootElement.appendChild(element);
//
// Required for Orange SMS
//
element = document.createElement("articleBodyNoTags");
element.appendChild(
document.createCDATASection(
HTMLProcessor.removeAllHtmlTags(articleBody)));
rootElement.appendChild(element);
element = document.createElement("bodyWithHtml");
element.appendChild(
document.createCDATASection("<![CDATA[" + this.fixRelativeUrls(this.body) + "]]>")
);
rootElement.appendChild(element);
//
// Plain Text Headline and Body for SMS Syndicators
// Uses the XML parser to reverse escaped html entities
//
String plainTextHeadline = this.headline;
String plainTextBody = articleBody;
String tmpXml = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>" +
"<root>" +
" <headline>" + articleHeader + "</headline>" +
" <body>" +
HTMLProcessor.removeAllHtmlTags(articleBody) +
"</body>" +
"</root>";
InputStream inputStream =
new ByteArrayInputStream(tmpXml.getBytes("UTF-8"));
try {
Document plainTextDom = domBuilder.parse(inputStream);
plainTextHeadline = XPathAPI.selectSingleNode(
plainTextDom.getDocumentElement(),
"//headline").getFirstChild().getNodeValue();
plainTextBody = XPathAPI.selectSingleNode(
plainTextDom.getDocumentElement(),
"//body").getFirstChild().getNodeValue();
} catch (Exception saxe) {
logger.error("Exception thrown parsing plaintext document: " +
saxe);
}
//
// Add plain text nodes to DOM
//
element = document.createElement("headlinePlainText");
element.appendChild(document.createCDATASection("<![CDATA[" +
plainTextHeadline +
"]]>"));
rootElement.appendChild(element);
element = document.createElement("articleBodyPlainText");
element.appendChild(
document.createCDATASection("<![CDATA[" +
plainTextBody +
"]]>"));
rootElement.appendChild(element);
//
// If we have a mobile image and it conforms to all the restrictions
// of being the correct size and a jpeg then add it to the XML
//
if (null != this.mobileImage && this.mobileImage.isValidMobileImage()
&& null != this.mobileImage.getData()) {
element = document.createElement("mobileImage");
childElement = document.createElement("name");
childElement.appendChild(document.createTextNode(
this.articleId + "_" + this.mobileImageId + "."
));
element.appendChild(childElement);
childElement = document.createElement("extension");
childElement.appendChild(document.createTextNode(
this.mobileImage.getExtension()
));
element.appendChild(childElement);
rootElement.appendChild(element);
}
//
// If we have teaser and header images supply them in this XML.
//
if (null != this.teaserImage) {
element = document.createElement("teaserImage");
element.appendChild(document.createTextNode(
this.teaserImage.getPath()
));
rootElement.appendChild(element);
}
if (null != this.headerImage) {
element = document.createElement("headerImage");
element.appendChild(document.createTextNode(
this.headerImage.getPath()
));
rootElement.appendChild(element);
}
// Check which objects are associated with the article and add the
// appropriate information
//
List<DetailObject> linkedObjectsList = this.linkedObjects.get(DetailType.PLAYER_KEY);
if (!ListUtils.isNullOrEmpty(linkedObjectsList)) {
for (DetailObject detailObj : linkedObjectsList) {
Player player = (Player) detailObj;
if (null != player) {
element = document.createElement("player");
childElement = document.createElement("ptvId");
childElement.appendChild(
document.createTextNode(
(new Integer(player.getPtvId())).toString()
)
);
element.appendChild(childElement);
childElement = document.createElement("paId");
childElement.appendChild(
document.createTextNode(
(new Integer(player.getPaId())).toString()
)
);
element.appendChild(childElement);
childElement = document.createElement("firstName");
childElement.appendChild(
document.createTextNode(player.getFirstName())
);
element.appendChild(childElement);
childElement = document.createElement("lastName");
childElement.appendChild(
document.createTextNode(player.getLastName())
);
element.appendChild(childElement);
childElement = document.createElement("initials");
childElement.appendChild(
document.createTextNode(player.getInitials())
);
element.appendChild(childElement);
rootElement.appendChild(element);
}
}
}
linkedObjectsList = this.linkedObjects.get(DetailType.MATCH_KEY);
if (!ListUtils.isNullOrEmpty(linkedObjectsList)) {
for (DetailObject detailObj : linkedObjectsList) {
Match match = (Match) detailObj;
if (null != match) {
element = document.createElement("match");
childElement = document.createElement("ptvId");
childElement.appendChild(
document.createTextNode(
(new Integer(match.getPtvId()).toString())
)
);
element.appendChild(childElement);
childElement = document.createElement("paId");
childElement.appendChild(
document.createTextNode((new Integer(match.getPaId()).toString()))
);
element.appendChild(childElement);
childElement = document.createElement("homeTeam");
childElement.appendChild(
document.createTextNode(match.getHomeTeam().getTeamName())
);
element.appendChild(childElement);
childElement = document.createElement("awayTeam");
childElement.appendChild(
document.createTextNode(match.getAwayTeam().getTeamName())
);
element.appendChild(childElement);
rootElement.appendChild(element);
}
}
}
linkedObjectsList = this.linkedObjects.get(DetailType.RALLY_KEY);
if (!ListUtils.isNullOrEmpty(linkedObjectsList)) {
for (DetailObject detailObj : linkedObjectsList) {
Rally rally = (Rally) detailObj;
if (null != rally) {
logger.debug("rally is " + rally.getId());
element = document.createElement("rally");
childElement = document.createElement("name");
childElement.appendChild(
document.createTextNode(rally.getName())
);
element.appendChild(childElement);
childElement = document.createElement("season");
childElement.appendChild(
document.createTextNode(
(new Integer(rally.getSeason())).toString()
)
);
element.appendChild(childElement);
rootElement.appendChild(element);
}
}
}
linkedObjectsList = this.linkedObjects.get(DetailType.RALLY_TEAM_KEY);
if (!ListUtils.isNullOrEmpty(linkedObjectsList)) {
for (DetailObject detailObj : linkedObjectsList) {
RallyTeam rallyTeam = (RallyTeam) detailObj;
if (null != rallyTeam) {
logger.debug("Rally team is " + rallyTeam.getId());
element = document.createElement("rallyTeam");
childElement = document.createElement("id");
childElement.appendChild(
document.createTextNode(
(new Integer(rallyTeam.getId())).toString()
)
);
element.appendChild(childElement);
childElement = document.createElement("name");
childElement.appendChild(
document.createTextNode(rallyTeam.getName())
);
element.appendChild(childElement);
rootElement.appendChild(element);
}
}
}
linkedObjectsList = this.linkedObjects.get(DetailType.RALLY_DRIVER_KEY);
if (!ListUtils.isNullOrEmpty(linkedObjectsList)) {
for (DetailObject detailObj : linkedObjectsList) {
Driver driver = (Driver) detailObj;
if (null != driver) {
logger.debug("driver is " + driver.getId());
element = document.createElement("driver");
childElement = document.createElement("id");
childElement.appendChild(
document.createTextNode(
(new Integer(driver.getId())).toString()
)
);
element.appendChild(childElement);
childElement = document.createElement("firstName");
childElement.appendChild(
document.createTextNode(driver.getFirstName())
);
element.appendChild(childElement);
childElement = document.createElement("lastName");
childElement.appendChild(
document.createTextNode(driver.getLastName())
);
element.appendChild(childElement);
childElement = document.createElement("initials");
childElement.appendChild(
document.createTextNode(driver.getInitial())
);
element.appendChild(childElement);
rootElement.appendChild(element);
}
}
}
}
} catch (FactoryConfigurationError fce){
ErrorLog.write("Error when formating PTV XML " + fce,
Article.class,
Level.ERROR,
logger);
document = null;
} catch (ParserConfigurationException pce){
ErrorLog.write("Error when parsing PTV XML " + pce,
Article.class,
Level.ERROR,
logger);
} catch (IOException ioe) {
ErrorLog.write("Error when formating PTV XML " + ioe,
Article.class,
Level.ERROR,
logger);
document = null;
}
return document;
}
/**
* Returns a random teaser video among the ones available for this article.
* @return
* @throws Exception
*/
public String getRandomTeaserVideo() throws Exception {
if (getTeaserVideos() != null && getTeaserVideos().length > 0) {
//
// Random index in the array
//
Random rg = new Random();
int index = rg.nextInt(this.teaserVideos.length);
return this.teaserVideos[index];
}
return null;
}
/**
* Copies the article to another site.
* @param site
* @param dbConnection
* @param logger
* @return The ID of the article created
*/
public Article copyArticleToSite(
Site site,
Connection dbConnection,
Logger logger) throws Exception {
//
// Create new article for the site
//
Article newArticle = Article.getInstance(site);
newArticle.setOwnedBy(site);
//
// Initialise things that need to be initialised
//
this.teaserVideos = getTeaserVideos(dbConnection, logger);
this.relatedArticleKeywords = getRelatedArticleKeywords(dbConnection, logger);
//
// Copy all values
//
newArticle.setHeadline(getHeadline());
newArticle.setTeaser(getTeaser());
newArticle.setSummary(getSummary());
//
// Removes images references from the body
// using a regular expression
//
String articleBody = getBody();
articleBody = articleBody.replaceAll("(?i)<img ([^>]*)([^/])>", "");
newArticle.setBody(articleBody);
newArticle.setIsGeneralArticle(isGeneralArticle());
newArticle.setOnHomePage(isOnHomePage());
newArticle.setSyndicated(isSyndicated());
newArticle.setMobileArticle(isMobileArticle());
newArticle.setPodcastArticle(isPodcastArticle());
newArticle.setGoogleVideoArticle(isGoogleVideoArticle());
newArticle.setAvailableForAlerts(isAvailableForAlerts());
newArticle.setDisplayOnNewsletter(isDisplayOnNewsletters());
newArticle.setVideoLo(getVideoLo());
newArticle.setVideoHi(getVideoHi());
newArticle.setVideoDownload(getVideoDownload());
newArticle.setTeaserVideos(getTeaserVideos());
newArticle.setRelatedArticleKeywords(getRelatedArticleKeywords());
newArticle.setVideoPlayCount(getVideoPlayCount());
newArticle.setAudioStream(getAudioStream());
newArticle.setFlashFile(getFlashFile());
newArticle.setVideoMedium(getVideoMedium());
newArticle.setPageRank(getPageRank());
newArticle.setHomePageRank(getHomePageRank());
newArticle.setCategory(getCategory());
//
// No images for rights issues
//
newArticle.setTeaserImage(null);
newArticle.setHeaderImage(null);
newArticle.setMobileImage(null);
newArticle.setVideoHoldingImage(null);
//
// Changing dates
//
newArticle.setArticleDate(new Date());
newArticle.setSiteRemovedDate(null);
newArticle.setSitePostedDate(null);
//
// Linked objects
//
getLinkedObjects().putAll(newArticle.getLinkedObjects());
return newArticle;
}
/**
* Initializes the HashMaps containing the configuration for teaser
* and healine limits for each site and article category.
* @param dbConnection
* @param logger
* @return
*/
public synchronized static void initSizeLimits(Connection dbConnection, Logger logger)
throws SQLException {
if (!Article.sizeLimitsInitialised) {
PreparedStatement statement = null;
try {
String sqlQuery =
"SELECT " +
"orgn_id, " +
"headline_size, " +
"teaser_size, " +
"catg_id " +
"FROM article_size_limits ";
statement = ConnectionPool.prepareStatement(
sqlQuery,
dbConnection,
logger
);
ResultSet rs = ConnectionPool.executeQuery(statement, dbConnection, logger);
while (rs.next()) {
//
// Processing the results.
//
int orgnId = rs.getInt("orgn_id");
//
// If we already have a configuration hashmap for this site,
// we're going to use it.
// If not, we create a new one that we will put in the main hashmap.
//
HashMap<Integer, Integer> headlineCategoryConfigHashMap = headlineSizeLimits.get(new Integer(orgnId));
if (headlineCategoryConfigHashMap == null) {
headlineCategoryConfigHashMap = new HashMap<Integer, Integer>();
}
HashMap<Integer, Integer> teaserCategoryConfigHashMap = teaserSizeLimits.get(new Integer(orgnId));
if (teaserCategoryConfigHashMap == null) {
teaserCategoryConfigHashMap = new HashMap<Integer, Integer>();
}
//
// We add the categoryId and the value of the limit in
// the "inner" hashmap.
//
int headlineLimit = rs.getInt("headline_size");
int teaserLimit = rs.getInt("teaser_size");
int categoryId = rs.getInt("catg_id");
if (rs.wasNull()) {
//
// If the category retrieved is null, this is the site-wide
// configuration, so we put that in the hashmap with a special id (HASHMAP_KEY_ORGANISATION).
//
headlineCategoryConfigHashMap.put(new Integer(Article.HASHMAP_KEY_ORGANISATION), new Integer(headlineLimit));
teaserCategoryConfigHashMap.put(new Integer(Article.HASHMAP_KEY_ORGANISATION), new Integer(teaserLimit));
}
else {
headlineCategoryConfigHashMap.put(new Integer(categoryId), new Integer(headlineLimit));
teaserCategoryConfigHashMap.put(new Integer(categoryId), new Integer(teaserLimit));
}
headlineSizeLimits.put(new Integer(orgnId), headlineCategoryConfigHashMap);
teaserSizeLimits.put(new Integer(orgnId), teaserCategoryConfigHashMap);
}
Article.sizeLimitsInitialised = true;
}
finally {
if(statement != null) {
try {
statement.close();
} catch (Exception e) {
logger.error("Could not close statement : " + e.getMessage(), e);
}
}
}
}
}
/**
* Initializes the information about automatic syndication
* @param dbConnection
* @param logger
* @return
*/
public synchronized static void initAutomaticSyndicationConfig(Connection dbConnection, Logger logger)
throws SQLException {
if (!Article.syndicationConfigInitialised) {
PreparedStatement statement = null;
try {
String sqlQuery =
"SELECT " +
"orgn_id, " +
"catg_id " +
"FROM article_automatic_syndication ";
statement = ConnectionPool.prepareStatement(
sqlQuery,
dbConnection,
logger
);
ResultSet rs = ConnectionPool.executeQuery(statement, dbConnection, logger);
while (rs.next()) {
//
// Processing the results.
//
int orgnId = rs.getInt("orgn_id");
//
// If we already have a configuration hashmap for this site,
// we're going to use it.
// If not, we create a new one that we will put in the main hashmap.
//
HashMap<Integer, Boolean> syndicationConfigHashMap = autoSyndicationConfig.get(new Integer(orgnId));
if (syndicationConfigHashMap == null) {
syndicationConfigHashMap = new HashMap<Integer, Boolean>();
}
//
// We add the categoryId
//
int categoryId = rs.getInt("catg_id");
syndicationConfigHashMap.put(new Integer(categoryId), Boolean.TRUE);
autoSyndicationConfig.put(new Integer(orgnId), syndicationConfigHashMap);
}
Article.syndicationConfigInitialised = true;
}
finally {
if(statement != null) {
try {
statement.close();
} catch (Exception e) {
logger.error("Could not close statement : " + e.getMessage(), e);
}
}
}
}
}
/**
* Gets the headline size limit configuration for a site.
*
* @param dbConnection
* @param logger
* @return the HashMap containing the configuration of headline
* size limits per category
*/
public static HashMap<Integer, Integer> getHeadlineSizeLimits(Site site,
Connection dbConnection,
Logger logger) throws SQLException {
//
// Initialise the data if necessary
//
if (!sizeLimitsInitialised) {
initSizeLimits(dbConnection, logger);
}
//
// Fetches the config for that site
//
if (site != null)
return headlineSizeLimits.get(new Integer(site.getId()));
else
return null;
}
/**
* Gets the teaser size limit configuration for a site.
*
* @param dbConnection
* @param logger
* @return the HashMap containing the configuration of headline
* size limits per category
*/
public static HashMap<Integer, Integer> getTeaserSizeLimits(Site site,
Connection dbConnection,
Logger logger) throws SQLException {
//
// Initialise the data if necessary
//
if (!sizeLimitsInitialised) {
initSizeLimits(dbConnection, logger);
}
//
// Fetches the config for that site
//
if (site != null)
return teaserSizeLimits.get(new Integer(site.getId()));
else
return null;
}
/**
* Gets the configuration for automatic syndication in the article editor
* @param site
* @param dbConnection
* @param logger
* @return
* @throws SQLException
*/
public static HashMap<Integer, Boolean> getAutomaticSyndicationConfig(Site site,
Connection dbConnection,
Logger logger) throws SQLException {
//
// Initialise the data if necessary
//
if (!syndicationConfigInitialised) {
initAutomaticSyndicationConfig(dbConnection, logger);
}
//
// Fetches the config for that site
//
if (site != null)
return autoSyndicationConfig.get(new Integer(site.getId()));
else
return null;
}
/**
* Flushes the headline size limits hashmap
*/
public static void flushData() {
Article.headlineSizeLimits = new HashMap<Integer, HashMap<Integer, Integer>>();
Article.teaserSizeLimits = new HashMap<Integer, HashMap<Integer, Integer>>();
Article.autoSyndicationConfig = new HashMap<Integer, HashMap<Integer, Boolean>>();
sizeLimitsInitialised = false;
syndicationConfigInitialised = false;
}
/**
* Returns the linked article id to the given detail id and the given detail
* type id that is written for the given category.
* For example, you can find the article on the match preview page that is linked to the given match.
*
* @param category the category that the article belongs to
* @param detailId the detail id (i.e. match id)
* @param detailTypeId the detail type id (i.e. 3 for Match)
* @param dbConnection
* @return the article id or 0 if none returned
* @throws SQLException
*/
public static int getLinkedArticleId(Category category, Site site, int detailId, int detailTypeId, Connection dbConnection) throws SQLException {
int ret = Curl.NO_DETAIL;
PreparedStatement preparedStatement = ConnectionPool.prepareStatement(
"SELECT a.artl_id " +
"FROM editorial_articles a, article_links l " +
"WHERE a.artl_id = l.artl_id " +
"AND a.orgn_club_id = ? " +
"AND a.catg_id = ? " +
"AND l.link_id = ? " +
"AND l.detail_type_id = ? " +
"ORDER BY a.artl_id DESC",
dbConnection,
logger
);
try {
preparedStatement.setInt(1, site.getId());
preparedStatement.setInt(2, category.getCategoryId());
preparedStatement.setInt(3, detailId);
preparedStatement.setInt(4, detailTypeId);
ResultSet resultSet = ConnectionPool.executeQuery(preparedStatement, dbConnection, logger);
if(resultSet.next()) {
ret = resultSet.getInt("artl_id");
}
} finally {
preparedStatement.close();
}
return ret;
}
/**
* Returns the ptv id of the Match - this is required for the DetailObject
* interface. Please use getPtvId or getPaId instead as appropriate
*
* @return Id for DetailObject interface
*/
public int getId() {
return this.getArticleId();
}
/**
* @return display name for DetailObject interface
*/
public String getName() {
return this.getHeadline();
}
/**
* @return Detail type id for DetailObject interface
*/
public int getDetailTypeId() {
return PageElement.DETAIL_TYPE_ARTICLE_ID;
}
/**
* Return an array of instances of the object based on their unique ids
*
* @param id Array of ids
* @param site Site
* @param dbConnection database connection
* @param logger Logger
*
* @return object instances
*/
public Article[] getInstances(int[] ids,
Site site,
Connection dbConnection,
Logger logger)
throws SQLException {
logger.debug("Retrieving articles via DetailObject interface for ids " +
ids);
Article[] articles = Article.getArticles(ids,
false,
false,
site,
dbConnection,
logger);
return articles;
}
/**
* This method returns all the properties that can be exposed.
* The key is of type <code>java.lang.String</code> class that
* represents the property name & value is of type
* <code>java.lang.Class</code> that represents the class , returned by
* invocation of method: getProperty(property)
*
* @return a map with all its property mapped
*/
public Map<String, Class> getPropertyMap() {
return propertyMap;
}
/**
* This method returns a value of a given property whose return type is of
* class represented by returnClass. The method return null, if either
* property is not supported by this DetailObject or returnClass of property
* does not match.
*
* @param property The name of the property
* @param returnClass The return type class of the property
* @return Object, value of a given property
*/
public Object getProperty(String property, Class returnClass) {
Object value = null;
if (isPropertyDefined(property, returnClass)) {
if (TITLE.equals(property)) {
value = this.getHeadline();
} else if(CURL.equals(property)) {
//try {
value = this.getCurl();
// } catch (URISyntaxException e) {
// e.printStackTrace();
// } catch (InvalidCurlException e) {
// e.printStackTrace();
// }
} else if(THUMBNAIL.equals(property)) {
Image[] defaultImages = {getHeaderImage(),getTeaserImage()};
Image defaultImage = null;
for(int i =0; i < defaultImages.length; i++) {
if(null != defaultImages[i]) {
defaultImage = defaultImages[i];
if(null != defaultImages[i].getThumbnail()) {
defaultImage = defaultImages[i].getThumbnail();
}
value = defaultImage;
break;
}
}
} else if (TEASER.equals(property)) {
value = this.getTeaser();
} else if (VIDEOHI.equals(property)) {
value = this.getVideoHi();
} else if (VIDEOLO.equals(property)) {
value = this.getVideoLo();
} else if (VIDEOMEDIUM.equals(property)) {
value = this.getVideoMedium();
} else if (TEASERIMAGEPATH.equals(property)) {
value = this.getTeaserImagePath();
} else if (HEADERIMAGEPATH.equals(property)) {
value = this.getHeaderImagePath();
} else if (DATE.equals(property)) {
value = this.getArticleDate();
} else if (CATEGORY.equals(property)) {
value = Integer.valueOf(this.getCategory().getCategoryId());
}
}
return value;
}
/**
* This method returns a value of a given property.
*
* @param property The name of the property
* @return Object, value of a given property
*/
public Object getProperty(String property) {
Object value = null;
if (isPropertyDefined(property)) {
value = getProperty(property, propertyMap.get(property));
}
return value;
}
/**
* This is a test that tells whether this DetailObject can expose the given
* property
*
* @param property A name of the property
* @return boolean true if the property is defined for this class
*/
public boolean isPropertyDefined(String property) {
return propertyMap.containsKey(property);
}
/**
* This is a test that tells whether this DetailObject can expose the given
* property whose return type is of class represented by returnClass
*
* @param property A name of the property
* @param returnClass The return type class of the property
* @return boolean true if the property is defined for this class
*/
public boolean isPropertyDefined(String property, Class returnClass) {
boolean isPropertyDefined = false;
if (isPropertyDefined(property)) {
isPropertyDefined = returnClass == propertyMap.get(property);
}
return isPropertyDefined;
}
/**
* This sets a value of a given property.
* If the property does not exist, this method will ignore the Object.
*
* @param property The name of the property
* @return value of a given property
*/
public void setProperty(String property, Object obj) {;}
//
// *********************************************************************************************
// Methods required to implement the NewsSiteMapEntry interface
// *********************************************************************************************
//
/**
* @return URL location of this article
*/
public String getURLLocation() {
String urlLocation = "";
Curl curl = null;
if (null != this.usedBy) {
curl = this.getCurl();
urlLocation = null != curl
? this.usedBy.getWwwDomain() + this.getCurl().toString()
: "";
}
return urlLocation;
}
/**
* @return Date when this article got last modified.
*/
public Date getLastModified() {
return this.getLastEditDate();
}
/**
* @return Priority of this article with respect to
* other site map entries.
*/
public int getPriority() {
int priority = (int) (SiteMap.DEFAULT_SITE_MAP_PRIORITY * 10);
if(null != this.page) {
priority = this.page.getSiteMapPriority();
}
return priority;
}
/**
* @return Behaviour of this article in terms of how
* often this article changes.
*/
public String getChangeFrequency() {
String changeFrequency = SiteMap.DEFAULT_SITE_MAP_CHANGE_FREQUENCY;
if(null != this.page) {
changeFrequency = this.page.getSiteMapChangeFrequency();
}
return changeFrequency;
}
public Date getPublicationDate() {
return getSitePostedDate();
}
public String[] getKeywords() {
return getRelatedArticleKeywords();
}
/**
* Returns the DetailObject instance of the specified detail
* type id linked to this article.
*
* @param detailTypeId The detail type id.
*
* @return the DetailObject instance of the specified detail
* type id linked to this article.
*
* @throws SQLException
*/
public DetailObject getLinkedObject(int detailTypeId) throws SQLException {
List<DetailObject> detailObjects = getLinkedObjectList(detailTypeId);
DetailObject detailObject = null;
if (!ListUtils.isNullOrEmpty(detailObjects)) {
detailObject = detailObjects.get(0);
}
return detailObject;
}
/**
* Returns the DetailObject instance of the specified detail
* type id linked to this article.
*
* @param detailTypeId The detail type id.
*
* @return the DetailObject instance of the specified detail
* type id linked to this article.
*
* @throws SQLException
*/
public List<DetailObject> getLinkedObjectList(int detailTypeId) throws SQLException {
List<DetailObject> detailObjects = null;
DetailType detailType = DetailType.getDetailType(detailTypeId);
if (detailType != null &&
this.linkedObjects != null &&
this.linkedObjects.size() > 0) {
detailObjects = this.linkedObjects.get(detailType.getArticleLinkMapKey());
}
return detailObjects;
}
/**
* Add the linked object
* @param detailType
* @param instanceId
* @param site
* @param conn
* @throws SQLException
*/
public void setLinkedObject(DetailType detailType, int instanceId, Site site, Connection conn)
throws SQLException {
DetailObject dObject = detailType.getDetailObjectInstance(instanceId, site, conn, logger);
setLinkedObject(detailType, dObject);
}
/**
* Add the linked object
* @param detailType
* @param detailObject
*/
public void setLinkedObject(DetailType detailType, DetailObject detailObject) {
if (detailType.getHasArticleLinkMapKey()) {
ArrayList<DetailObject> detailObjs = linkedObjects.get(detailType.getArticleLinkMapKey());
if (detailObjs == null) {
detailObjs = new ArrayList<DetailObject>();
linkedObjects.put(detailType.getArticleLinkMapKey(), detailObjs);
}
detailObjs.add(detailObject);
}
}
/**
* Returns the <code>ExternalUrl</code> associated to this article, or null if there's none.
* @return
*/
public ExternalUrl getExternalUrl(){
List<DetailObject> list = linkedObjects.get(DetailType.EXTERNAL_URL_KEY);
if (!ListUtils.isNullOrEmpty(list)){
return (ExternalUrl) list.get(0);
}
return null;
}
/**
* Returns the <code>MavenVideo</code> associated to this article, or null if there's none.
* @return
*/
public MavenVideo getMavenVideo() {
MavenVideo ret = null;
List<DetailObject> list = linkedObjects.get(DetailType.MAVEN_VIDEO_KEY);
if (!ListUtils.isNullOrEmpty(list)){
ret = (MavenVideo) list.get(0);
}
return ret;
}
/**
* Returns the <code>BrightcoveVideo</code> associated to this article, or null if there's none.
* @return
*/
public BrightcoveVideo getBrightcoveVideo() {
BrightcoveVideo ret = null;
List<DetailObject> list = linkedObjects.get(DetailType.BRIGHTCOVE_VIDEO_KEY);
if (!ListUtils.isNullOrEmpty(list)){
ret = (BrightcoveVideo) list.get(0);
}
return ret;
}
/**
* @return The URL pointing to the live feed, or NULL if there isn't one.
*/
public FlashStreamURL getLiveSWFStreamURL() {
FlashStreamURL url = null;
List<DetailObject> list = linkedObjects.get(DetailType.LIVE_SWF_URL);
if (!ListUtils.isNullOrEmpty(list)){
url = (FlashStreamURL) list.get(0);
}
return url;
}
//
// *********************************************************************************************
// Method required to implement the DetailObject interface
// *********************************************************************************************
//
/**
* Encapsulates the detail object meta information.
*/
public DetailObjectMetaInformation detailObjectMetaInformation = new DetailObjectMetaInformation();
/**
* Returns the detail object meta information (rating, views, comments ....).
*
* @return the detail object meta information.
*/
public DetailObjectMetaInformation getDetailObjectMetaInformation() {
return this.detailObjectMetaInformation;
}
//
// *********************************************************************************************
// Methods required to implement the DetailObjectListable interface
// *********************************************************************************************
//
/**
* Checks whether the detail object this specific type of ServiceRequest
*
* @param sRequest
* @return
*/
public boolean supports(ServiceRequest sRequest) {
return (
StringUtils.equalsSafe(sRequest.getName(), DateCategoryListingRequest.REQUEST_NAME)
);
}
public DetailObject[] getInstances(ServiceRequest sRequest, Connection conn)
throws Exception {
DetailObject[] detailObjects = null;
if (supports(sRequest)) {
if (StringUtils.equalsSafe(sRequest.getName(), DateCategoryListingRequest.REQUEST_NAME)) {
DateCategoryListingRequest req = (DateCategoryListingRequest) sRequest;
//
// This search could return a lot of articles, and we only want to pull
// back skeleton information for them. For performance reasons, we do this
// query here, rather than using one of the getArticles methods which will
// also result in linked information being fetched back too.
//
//
// Use >= and < to check for article date instead of BETWEEN as we want
// to include articles written on start dates too.
//
// Returned data is ordered by the date, with the latest articles being
// on the top of the list.
//
boolean useLinkedDateIfPresent = req.isUseLinkedDate();
int paramCount = 1;
PreparedStatement query;
if (!useLinkedDateIfPresent) {
query = ConnectionPool.prepareStatement(
SELECT_FOR_LISTING_BY_POSTED_DATE, conn, logger);
} else {
query = ConnectionPool.prepareStatement(
SELECT_FOR_LISTING_BY_LINKED_AND_POSTED_DATE_SINGLE_CATEGORY, conn, logger);
query.setInt(paramCount++, PageElement.DETAIL_TYPE_DATE_ID);
}
try {
//
// Set parameters to be passed to the above SQL query.
//
query.setDate(paramCount++, new java.sql.Date(req.getStartDate().getTime()));
query.setDate(paramCount++, new java.sql.Date(req.getEndDate().getTime()));
query.setInt(paramCount++, req.getSite().getId());
//
// The category ID will either be a number, or a number prefixed with
// the string "frompage". When it's a number on its own, it directly
// refers to a category ID. When it's prefixed with "frompage", the
// number is a page ID and the category is the category of that page.
//
int categoryId;
if (req.getCategoryId().startsWith(
DateCategoryListingStrategy.CATEGORY_PAGE_PREFIX)) {
int pageId = Integer.parseInt(
req.getCategoryId().substring(
DateCategoryListingStrategy.CATEGORY_PAGE_PREFIX.length()
)
);
//
// We now have a page ID. Look for that page, and then for its
// category. If we can't find the page, or it doesn't have a category
// throw an exception.
//
Page page = Page.getPage(pageId, req.getSite(), logger);
if (null == page) {
throw new Exception("Page " + pageId + " not found.");
}
if (null == page.getCategory()) {
throw new Exception("Page " + pageId + " has no category.");
}
categoryId = page.getCategory().getCategoryId();
} else {
//
// Treat the category ID as a number. If it's not, the parsing
// exception will be propagated down from here, and handled by the
// detail object servlet's framework.
//
categoryId = Integer.parseInt(req.getCategoryId());
}
query.setInt(paramCount++, categoryId);
//
// Run the query and build up our article objects.
//
ResultSet rs = ConnectionPool.executeQuery(query, conn, logger);
//
// Loop through results and build articles for each row returned. We
// only build the skeletons of articles, as there's no need to fetch
// back images or other linked objects.
//
ArrayList<Article> articleList;
try {
articleList = new ArrayList<Article>();
while (rs.next()) {
Article article = new Article();
article.setArticleFields(rs, req.getSite(), false, conn, logger);
articleList.add(article);
}
//
// Link up the pages correctly on all of the fetched articles.
//
setPages(
articleList.toArray(new Article[articleList.size()]),
conn,
logger
);
//
// Add linked objects and images
//
if (articleList.size() > 0) {
detailObjects = new DetailObject[articleList.size()];
detailObjects = articleList.toArray(detailObjects);
}
} finally {
rs.close();
}
} finally {
query.close();
}
}
} else {
throw new Exception("Invalid/Unknown Service Request ["+sRequest.getName()+"]");
}
return detailObjects;
}
/**
* Return this article's information as xml.
*/
public String getXml() {
StringBuffer buffer = new StringBuffer();
buffer.append("<article date='");
buffer.append(DateTimeUtils.dateToString(getArticleDate(), DateTimeUtils.FMT_ISO_DATE));
buffer.append("'");
buffer.append(" id='");
buffer.append(getArticleId());
buffer.append("'>");
buffer.append("<headline><![CDATA[");
buffer.append(getHeadline());
buffer.append("]]></headline>");
if (getCurl() != null) {
buffer.append("<detailPageUrl>");
buffer.append(getCurl());
buffer.append("</detailPageUrl>");
}
buffer.append("</article>");
return buffer.toString();
}
/**
* Return this users information as xml
*/
public String getXml(String type) throws Exception{
String xml = null;
if (StringUtils.equalsSafe(type, DetailObjectListable.DEFAULT_XML)) {
xml = getXml();
} else {
throw new Exception("Unknown XML type ["+type+"] for class " + this.getClass().getName());
}
return xml;
}
//
// *********************************************************************************************
// Method required to implement the LuceneIndexable interface
// *********************************************************************************************
//
private static final String ARTICLE_HEADLINE_INDEX_FIELD_NAME = "headline";
private static final String ARTICLE_TEASER_TEXT_INDEX_FIELD_NAME = "teaserText";
private static final String ARTICLE_PAGE_CATEGORY_ID_INDEX_FIELD_NAME = "pageCategoryId";
private static final String ARTICLE_PAGE_NAME_INDEX_FIELD_NAME = "pageName";
private static final String ARTICLE_DETAIL_PAGE_URL_INDEX_FIELD_NAME = "detailPageUrl";
private static final String ARTICLE_TEASER_IMAGE_URL_INDEX_FIELD_NAME = "teaserImageUrl";
private static final String ARTICLE_TEASER_IMAGE_ALT_INDEX_FIELD_NAME = "teaserImageAlt";
private static final String ARTICLE_TEASER_IMAGE_HEIGHT_INDEX_FIELD_NAME = "teaserImageHeight";
private static final String ARTICLE_TEASER_IMAGE_WIDTH_INDEX_FIELD_NAME = "teaserImageWidth";
private static final String ARTICLE_HEADER_IMAGE_URL_INDEX_FIELD_NAME = "headerImageUrl";
private static final String ARTICLE_HEADER_IMAGE_ALT_INDEX_FIELD_NAME = "headerImageAlt";
private static final String ARTICLE_HEADER_IMAGE_HEIGHT_INDEX_FIELD_NAME = "headerImageHeight";
private static final String ARTICLE_HEADER_IMAGE_WIDTH_INDEX_FIELD_NAME = "headerImageWidth";
private static final String ARTICLE_KEYWORD_INDEX_FIELD_NAME = "keyword";
private static final String IS_VIDEO_ARTICLE_INDEX_FIELD_NAME = "isVideoArticle";
private static final String ARTICLE_VIDEO_DURATION_INDEX_FIELD_NAME = "videoDuration";
private static final String ARTICLE_VIDEO_ID_INDEX_FIELD_NAME = "videoId";
private static final String ARTICLE_VIDEO_CLIP_ID_INDEX_FIELD_NAME = "videoClipId";
private static final String ARTICLE_AVAILABLE_IN_PLAYER_FIELD_NAME = "availableInPlayer";
private static final String ARTICLE_VIDEO_CLIP_LO_SRC_FIELD_NAME = "videoLoSrc";
private static final String ARTICLE_VIDEO_CLIP_MED_SRC_FIELD_NAME = "videoMedSrc";
private static final String ARTICLE_VIDEO_CLIP_HI_SRC_FIELD_NAME = "videoHiSrc";
private static final String ARTICLE_VIDEO_SYNDICATION_TYPE = "syndicationType";
private static final String ARTICLE_RANK = "rank";
private static final String ARTICLE_HOME_PAGE = "homePage";
/**
* Returns the published date field to add to the Lucene Document when indexing.
*
* @param dbConnection Connection to the database.
* @param logger Access to the logs files.
*
* @return the publish Date field to add to the Lucene Document when indexing.
*
* @throws Exception
*/
public Field getPublishedDateField(Site site, Connection dbConnection, Logger logger) throws Exception {
return new Field(
LuceneIndexFieldsUtils.PUBLISHED_DATE_INDEX_FIELD_NAME,
this.articleDate != null?new SimpleDateFormat(LuceneIndexFieldsUtils.DATE_INDEX_FIELD_DATE_FORMAT).format(this.articleDate):"",
Field.Store.YES,
Field.Index.ANALYZED
);
}
/**
* Returns the last updated date field to add to the Lucene Document when indexing.
*
* @param dbConnection Connection to the database.
* @param logger Access to the logs files.
*
* @return the last updated date field to add to the Lucene Document when indexing.
*
* @throws Exception
*/
public Field getLastUpdatedDateField(Site site, Connection dbConnection, Logger logger) throws Exception {
return new Field(
LuceneIndexFieldsUtils.LAST_UPDATED_DATE_INDEX_FIELD_NAME,
this.lastEditDate != null?new SimpleDateFormat(LuceneIndexFieldsUtils.DATE_INDEX_FIELD_DATE_FORMAT).format(this.lastEditDate):"",
Field.Store.YES,
Field.Index.NOT_ANALYZED
);
}
/**
* Returns the content owner fields to add to the Lucene Document when indexing.
*
* @param dbConnection Connection to the database.
* @param logger Access to the logs files.
*
* @return the content owner fields to add to the Lucene Document when indexing.
*
* @throws Exception
*/
public ArrayList<Field> getContentOwnerFields(Site site, Connection dbConnection, Logger logger) throws Exception {
ArrayList<Field> contentOwnerFieldList = new ArrayList<Field>();
//
// Articles have no owner user.
//
contentOwnerFieldList.add(new Field(
LuceneIndexFieldsUtils.CONTENT_OWNER_ID_FIELD_NAME,
"",
Field.Store.YES,
Field.Index.NOT_ANALYZED)
);
return contentOwnerFieldList;
}
/**
* Returns the field that will be scanned for free text searches.
*
* @return the field that will be scanned for free text searches.
*/
public Field getFreeTextIndexingField(Site site, Connection dbConnection, Logger logger) throws Exception {
//
// Build a composite string value with all the contents that will need
// to be scanned by default when making a freetext search on articles.
//
StringBuffer freeTextSearchContent = new StringBuffer();
LuceneIndexFieldsUtils.addValueToFreeTextField(freeTextSearchContent, this.headline);
LuceneIndexFieldsUtils.addValueToFreeTextField(freeTextSearchContent, this.teaser);
LuceneIndexFieldsUtils.addValueToFreeTextField(freeTextSearchContent, this.body);
//
// Add the keywords to the free text search Content.
//
this.getRelatedArticleKeywords(dbConnection, logger);
if (this.relatedArticleKeywords != null && this.relatedArticleKeywords.length > 0) {
for (String keyword:this.relatedArticleKeywords) {
LuceneIndexFieldsUtils.addValueToFreeTextField(freeTextSearchContent, keyword);
}
}
//
// Create the Lucene Field instance to return.
//
Field freeTextField = new Field(
LuceneIndexFieldsUtils.FREETEXT_INDEX_FIELD_NAME,
freeTextSearchContent.toString().toLowerCase(),
Field.Store.NO,
Field.Index.ANALYZED
);
return freeTextField;
}
/**
* Returns The list of type specific Lucene Field instances.
*
* @return the list of type specific Lucene Field instances.
*/
public ArrayList<Field> getTypeSpecificIndexingFields(Site site,
Connection dbConnection,
Logger logger) throws Exception {
ArrayList<Field> typeSpecificFieldList = new ArrayList<Field>();
//
// Headline.
//
typeSpecificFieldList.add(new Field(
ARTICLE_HEADLINE_INDEX_FIELD_NAME,
StringUtils.returnBlankIfNull(this.getHeadline()),
Field.Store.YES,
Field.Index.ANALYZED)
);
//
// Teaser Text.
//
typeSpecificFieldList.add(new Field(
ARTICLE_TEASER_TEXT_INDEX_FIELD_NAME,
StringUtils.returnBlankIfNull(this.getTeaser()),
Field.Store.YES,
Field.Index.ANALYZED)
);
//
// Page category id.
//
typeSpecificFieldList.add(new Field(
ARTICLE_PAGE_CATEGORY_ID_INDEX_FIELD_NAME,
String.valueOf(this.category != null?this.category.getCategoryId():-1),
Field.Store.YES,
Field.Index.NOT_ANALYZED)
);
//
// Page name.
//
typeSpecificFieldList.add(new Field(
ARTICLE_PAGE_NAME_INDEX_FIELD_NAME,
String.valueOf(this.page != null?this.page.getName():-1),
Field.Store.YES,
Field.Index.NOT_ANALYZED)
);
//
// Detail page url.
//
String detailPageUrl = "";
ExternalUrl externalUrl = (ExternalUrl) this.getLinkedObject(PageElement.DETAIL_TYPE_EXTERNAL_URL_ID);
if (null == externalUrl) {
if (null != this.getCurl()){
detailPageUrl = this.getCurl().toString();
}
} else {
detailPageUrl = externalUrl.getExternalUrl();
}
typeSpecificFieldList.add(new Field(
ARTICLE_DETAIL_PAGE_URL_INDEX_FIELD_NAME,
detailPageUrl,
Field.Store.YES,
Field.Index.NOT_ANALYZED)
);
//
// Teaser Image url.
//
typeSpecificFieldList.add(new Field(
ARTICLE_TEASER_IMAGE_URL_INDEX_FIELD_NAME,
this.getTeaserImagePath(dbConnection),
Field.Store.YES,
Field.Index.NOT_ANALYZED)
);
//
// Teaser Image alt.
//
typeSpecificFieldList.add(new Field(
ARTICLE_TEASER_IMAGE_ALT_INDEX_FIELD_NAME,
(this.teaserImage != null && this.teaserImage.getAltText() != null?this.teaserImage.getAltText():"-1"),
Field.Store.YES,
Field.Index.NOT_ANALYZED)
);
//
// Teaser Image height.
//
typeSpecificFieldList.add(new Field(
ARTICLE_TEASER_IMAGE_HEIGHT_INDEX_FIELD_NAME,
String.valueOf((this.teaserImage != null?this.teaserImage.getHeight():-1)),
Field.Store.YES,
Field.Index.NOT_ANALYZED)
);
//
// Teaser Image width.
//
typeSpecificFieldList.add(new Field(
ARTICLE_TEASER_IMAGE_WIDTH_INDEX_FIELD_NAME,
String.valueOf((this.teaserImage != null?this.teaserImage.getWidth():-1)),
Field.Store.YES,
Field.Index.NOT_ANALYZED)
);
//
// Header Image url.
//
typeSpecificFieldList.add(new Field(
ARTICLE_HEADER_IMAGE_URL_INDEX_FIELD_NAME,
this.getHeaderImagePath(dbConnection),
Field.Store.YES,
Field.Index.NOT_ANALYZED)
);
//
// Header Image alt.
//
typeSpecificFieldList.add(new Field(
ARTICLE_HEADER_IMAGE_ALT_INDEX_FIELD_NAME,
(this.headerImage != null && this.headerImage.getAltText() != null?this.headerImage.getAltText():"-1"),
Field.Store.YES,
Field.Index.NOT_ANALYZED)
);
//
// Header Image height.
//
typeSpecificFieldList.add(new Field(
ARTICLE_HEADER_IMAGE_HEIGHT_INDEX_FIELD_NAME,
String.valueOf((this.headerImage != null?this.headerImage.getHeight():-1)),
Field.Store.YES,
Field.Index.NOT_ANALYZED)
);
//
// Header Image width.
//
typeSpecificFieldList.add(new Field(
ARTICLE_HEADER_IMAGE_WIDTH_INDEX_FIELD_NAME,
String.valueOf((this.headerImage != null?this.headerImage.getWidth():-1)),
Field.Store.YES,
Field.Index.NOT_ANALYZED)
);
//
// Is video article field.
//
typeSpecificFieldList.add(new Field(
IS_VIDEO_ARTICLE_INDEX_FIELD_NAME,
this.isVideoArticle()?"Y":"N",
Field.Store.YES,
Field.Index.NOT_ANALYZED)
);
//
// Linked video duration, id and clip id - have to assume the first video and then first clip.
// Also index syndication type to filter audio/video
//
float videoDuration = 0;
int videoId = 0;
int clipId = 0;
String syndicationType = null;
Video[] videos = getLinkedVideos();
if(!ArrayUtils.isNullOrEmpty(videos) && videos[0] != null) {
videoDuration = videos[0].getDuration();
videoId = videos[0].getId();
syndicationType = videos[0].getType().getSyndicationType();
Collection<Clip> clips = videos[0].getClips();
if(clips != null && clips.size() > 0) {
clipId = clips.iterator().next().getId();
}
}
typeSpecificFieldList.add(new Field(
ARTICLE_VIDEO_DURATION_INDEX_FIELD_NAME,
String.valueOf(videoDuration),
Field.Store.YES,
Field.Index.NOT_ANALYZED)
);
typeSpecificFieldList.add(new Field(
ARTICLE_VIDEO_ID_INDEX_FIELD_NAME,
String.valueOf(videoId),
Field.Store.YES,
Field.Index.NOT_ANALYZED)
);
typeSpecificFieldList.add(new Field(
ARTICLE_VIDEO_CLIP_ID_INDEX_FIELD_NAME,
String.valueOf(clipId),
Field.Store.YES,
Field.Index.NOT_ANALYZED)
);
if(!StringUtils.isNullOrEmpty(this.getVideoLo())) {
typeSpecificFieldList.add(new Field(
ARTICLE_VIDEO_CLIP_LO_SRC_FIELD_NAME,
this.getVideoLo(),
Field.Store.YES,
Field.Index.NOT_ANALYZED)
);
}
if(!StringUtils.isNullOrEmpty(this.getVideoMedium())) {
typeSpecificFieldList.add(new Field(
ARTICLE_VIDEO_CLIP_MED_SRC_FIELD_NAME,
this.getVideoMedium(),
Field.Store.YES,
Field.Index.NOT_ANALYZED)
);
}
if(!StringUtils.isNullOrEmpty(this.getVideoHi())) {
typeSpecificFieldList.add(new Field(
ARTICLE_VIDEO_CLIP_HI_SRC_FIELD_NAME,
this.getVideoHi(),
Field.Store.YES,
Field.Index.NOT_ANALYZED)
);
}
//
// Add the video syndication type to filter audio/video
//
if (!StringUtils.isNullOrEmpty(syndicationType)) {
typeSpecificFieldList.add(new Field(
ARTICLE_VIDEO_SYNDICATION_TYPE,
syndicationType,
Field.Store.YES,
Field.Index.ANALYZED)
);
}
//
// Add the keywords fields.
//
if (this.relatedArticleKeywords != null && this.relatedArticleKeywords.length > 0) {
for (String keyword:this.relatedArticleKeywords) {
typeSpecificFieldList.add(new Field(
ARTICLE_KEYWORD_INDEX_FIELD_NAME,
keyword,
Field.Store.YES,
Field.Index.ANALYZED)
);
}
}
//
// Add linked types and linked objects
// We had them according to the format <linkedObject_typeId>instanceId</linkedObject_typeId>
//
// For example the match 35123 will be represented as:
// <linkedObject_3>35123</linkedObject_3>
//
if (this.linkedObjects != null && !this.linkedObjects.isEmpty()) {
//
// We keep a list of detail type IDs to index so that we don't index the same
// one twice (not knowing how Lucene will react to this - might give a stronger weight
// to results or something like that).
//
Set<Integer> detailTypeIdsToIndex = new HashSet<Integer>();
for (Map.Entry<String,ArrayList<DetailObject>> linkedObjectEntry : linkedObjects.entrySet()) {
//
// Each entry of the map is a String (detailType) mapping to a
// collection of detail objects. We iterate on the objects in the value set,
// and index the type and instance Id for each.
//
for (DetailObject linkedObject : linkedObjectEntry.getValue()) {
if (!detailTypeIdsToIndex.contains(linkedObject.getDetailTypeId())) {
detailTypeIdsToIndex.add(linkedObject.getDetailTypeId());
}
typeSpecificFieldList.add(new Field(
LuceneUtils.buildFieldName(LuceneIndexFieldsUtils.LINKED_OBJECT_INDEX_FIELD_NAME, linkedObject.getDetailTypeId()),
new Integer(linkedObject.getId()).toString(),
Field.Store.YES,
Field.Index.ANALYZED)
);
}
}
//
// Index detail types
//
for (Integer detailTypeId : detailTypeIdsToIndex) {
typeSpecificFieldList.add(new Field(
LuceneIndexFieldsUtils.LINKED_OBJECT_INDEX_FIELD_NAME,
detailTypeId.toString(),
Field.Store.YES,
Field.Index.ANALYZED)
);
}
}
//
// Index the "Player" flag
//
typeSpecificFieldList.add(new Field(
ARTICLE_AVAILABLE_IN_PLAYER_FIELD_NAME,
StringUtils.toBoolYNString(isAvailableInPlayer()),
Field.Store.YES,
Field.Index.ANALYZED)
);
//
//Rank
//
if (pageRank == NOT_RANKED){
pageRank = Integer.MAX_VALUE;
}
typeSpecificFieldList.add(new Field(
ARTICLE_RANK,
String.valueOf(pageRank),
Field.Store.YES,
Field.Index.ANALYZED)
);
//
// Homepage flag
//
typeSpecificFieldList.add(new Field(
ARTICLE_HOME_PAGE,
StringUtils.toBoolYNString(onHomePage),
Field.Store.YES,
Field.Index.ANALYZED)
);
return typeSpecificFieldList;
}
/**
* Get Detail page url from system split out from the
* get details so that we can return it to the related article renderer.
*
* @return
* @throws SQLException
*/
public String getExternalURL() throws SQLException {
String externalUrlValue = "";
ExternalUrl externalUrl = (ExternalUrl) this.getLinkedObject(PageElement.DETAIL_TYPE_EXTERNAL_URL_ID);
if(externalUrl == null) {
externalUrlValue = "";
} else {
externalUrlValue = (externalUrl.getExternalUrl()).replace('"',' ');
}
return externalUrlValue;
}
/**
* Returns whether the detail object is expired, so we should remove it from
* the index instead of update it.
*
* @return true - if the detail object is expired.
* false - otherwise.
*/
public boolean expireFromSearchIndex(Site site, Connection dbConnection, Logger logger) throws Exception {
return this.isExpired();
}
//
// *********************************************************************************************
// Method required to implement the SEOFriendlyURLEnabled interface
// *********************************************************************************************
//
/**
* Returns the <detail_object_descriptor> part to use for the SEO friendly
* URLs format used to access this type of detail objects.
*
* NOTE: The SEO friendly URLs we use follow the format below
*
* <PRE>
* http://<domain>/<optional_prefix>/<detail_object_descriptor>_
* <page_id>_<detail_id>/<curl_filename>
* </PREV>
*
* NOTE: Don't worry about formatting the string returned by this method.
* It doesn't matter if this string contains strange characters or spaces,
* as it will be automatically formatted at a later stage.
*
* @return the string to use for the <detail_object_descriptor> part
* of the SEO friendly URLs.
*/
public String getSEOFriendlyObjectDescriptor() {
return this.headline;
}
/**
* Returns a classifying subfolder to spread a little bit the
* urls in different subfolders. We had a problem were for
* articles we were hitting the 32k limit unix imposes in
* the number of subfolders.
* Depending on the detail object this can be a different thing,
* for example for articles/videos and other detail objects
* that have a date, this can be the published date of the
* article.
*
* @return a classifying subfolder string.
*/
public String getSEOFriendlyClassifyingSubfolder() {
return SEOURLsUtils.formatDateForInclusionInSEOFriendlyURL(this.articleDate);
}
/**
* {@code PageReference} encapsulates the information necessary to generate a
* URI reference for an article page.
*
* <p>Articles can be displayed (in their entirety) in one of two ways:</p>
* <ul>
* <li>On a {@link Page} that has it's detail type set to {@link ArticleDetail},
* using something like an Article Detail Renderer.</li>
* <li>On a {@link Page} that has the same detail type as at least one of
* the {@link DetailObject} types that have been linked to the article,
* using something like a Custom Linked Article Detail.</li>
* </ul>
*
* <p>For flushing purposes we need to extract this information without
* altering the internal state of the article. Using this inner class we can
* get article page references for multiple sites (ie syndicated content) for
* easy use in content flushing.</p>
*/
public static class PageReference {
/**
* The {@link Page} that can be used to display a particular article.
*/
private Page page = null;
/**
* The required {@link DetailObject} for the page.
*/
private DetailObject linkedObject = null;
/**
* Construct a new instance of <tt>PageReference</tt>.
*
* @param page The {@link Page} to use.
* @param linkedObject The {@link DetailObject} the page would use (ie the article)
*/
public PageReference(final Page page, final DetailObject linkedObject) {
super();
this.page = page;
this.linkedObject = linkedObject;
}
/**
* Get the {@link Page} that should be used to display the {@link DetailObject}
* associated with this {@code PageReference}.
*
* @return The {@link Page} for this {@code PageReference}.
*/
public Page getPage() {
return this.page;
}
/**
* Get the {@link DetailObject} that has been associated with the {@link Page}
* in this {@code PageReference}.
*
* @return The associated {@link DetailObject} for this {@code PageReference}.
*/
public DetailObject getLinkedObject() {
return this.linkedObject;
}
/**
* Get a relative URI that can be used to reference the {@link Page} and
* {@link DetailObject} combination stored in this {@code PageReference}.
*
* @return The {@link Curl} for this {@code PageReference}. If no {@link Page}
* or {@link DetailObject} has been supplied, then this will return
* {@code null}.
*/
public Curl getCurl() {
Curl curl;
if(page != null) {
if(linkedObject != null) {
curl = page.getCurl(linkedObject);
} else {
curl = page.getCurl();
}
} else {
curl = null;
}
return curl;
}
}
/**
* Internal class used to do the flushing of the article in its own thread.
*/
private static class ArticleFlushingTask implements Runnable {
/**
* Database connection used for the flushing.
*/
private Connection dbConnection = null;
/**
* List of article to flush.
*/
private Set<Article> articles = null;
/**
* Constructor.
*
* @param articles Set or articles to flsuh.
*/
public ArticleFlushingTask(Set<Article> articles) {
this(articles, null);
};
/**
* Constructor that enables to pass in a database connection.
*
* @param articles Set of articles to flush.
* @param dbConnection Connection to use for the flushing.
*/
public ArticleFlushingTask(Set<Article> articles, Connection dbConnection) {
this.articles = articles;
this.dbConnection = dbConnection;
}
/**
* Attempt to flush all disk files and in-memory content for a particular
* article on a given site.
*
* @param htmlCache The {@link HtmlCache} to decache the article from.
* @param site The {@link Site} to decache the article for.
* @param article The {@link Article} to decache.
*/
private void decacheArticleForSite(final HtmlCache htmlCache, final Site site, final Article article) {
//
// Flush all the page elements that support
// the ARTICLE detail type id for every one
// the article ids to flush.
//
htmlCache.decacheDetailType(
site,
PageElement.DETAIL_TYPE_ARTICLE_ID,
article.articleId,
false,
dbConnection,
logger
);
//
// Flush all the page elements that support
// any of the detail types of any of the
// detail objects linked to the articles.
//
Iterator<String> iter = article.linkedObjects.keySet().iterator();
while (iter.hasNext()) {
List<DetailObject> detailObjects = article.linkedObjects.get(iter.next());
if (!ListUtils.isNullOrEmpty(detailObjects)) {
for (DetailObject detailObject : detailObjects) {
htmlCache.decacheDetailType(
site,
detailObject.getDetailTypeId(),
detailObject.getId(),
false,
dbConnection,
logger
);
}
}
}
}
/**
* Method that executes when the task is run. Does the
* actual flushing of the articles.
*/
public void run() {
boolean connectionCreated = false;
ConnectionPool connectionPool = null;
try {
//
// If a connection hasn't been provided, create one.
//
if (this.dbConnection == null) {
connectionCreated = true;
connectionPool = ConnectionPool.getInstance("ArtlCtl", logger);
this.dbConnection = connectionPool.getConnection(true, logger);
}
//
// FLUSH DETAIL TYPES
//
if (articles != null && articles.size() > 0) {
HtmlCache htmlCache = HtmlCache.getInstance();
for (Article article: articles) {
if (article != null && (article.isLive() || article.isExpired())) {
try {
final Site[] articleSites = getArticleFlushingSites(article);
// articleSites should never be empty, so this is safe.
for(Site articleSite: articleSites) {
decacheArticleForSite(htmlCache, articleSite, article);
}
} catch (Exception e) {
logger.error("Exception flushing detail types for article id [" + article.getId() + "].", e);
}
}
}
//
// FLUSH ASSOCIATED PAGE ELEMENT INSTANCES.
//
flushAssociatedPageElementInstances(articles, dbConnection);
//
// FLUSH ASSOCIATED VIDEO TYPES.
//
flushAssociatedVideoTypes(articles, dbConnection);
//
// FLUSH ASSOCIATED PATHS.
//
flushAssociatedPaths(articles, dbConnection);
}
} catch (Throwable e) {
StringBuilder error = new StringBuilder("Exception while flushing article ids [ ");
if (articles != null) {
for (Article article:articles) {
if (article != null) {
error.append(article.getId());
}
}
}
error.append("]. Exception:[").append(e);
logger.error(error.toString(),e);
} finally {
//
// Don't forget to return the connection in case one was
// created by this thread.
//
if (connectionCreated && connectionPool != null && dbConnection != null) {
try {
connectionPool.returnConnection(dbConnection, logger);
} catch (Exception e) {
logger.error("Exception returning connection to pool during article flushing.",e);
}
}
}
}
}
public Collection<ServiceRequest> getSupportedRequests() {
List<ServiceRequest> requests = new ArrayList<ServiceRequest>();
requests.add(new DateCategoryListingRequest(null, null, null, null, null)); //this is the only request mentioned in supports().
return requests;
}
public Integer count(ServiceRequest request, Connection connection) {
throw new UnsupportedOperationException();
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment