-
-
Save FauxFaux/1064544 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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("&","_") +"\",\"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 '&' and after | |
// running this through XML Document Builder, we have double escaped | |
// '&', we revert escaping here. And also, replace all ' ' 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("&", "&"); | |
headline = headline.replaceAll(" ", " "); | |
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