Coverage Report - no.sesat.search.view.output.SyndicationGenerator
 
Classes in this File Line Coverage Branch Coverage Complexity
SyndicationGenerator
0%
0/125
0%
0/34
0
SyndicationGenerator$1
0%
0/3
N/A
0
SyndicationGenerator$Context
N/A
N/A
0
SyndicationGenerator$SyndicationNotSupportedException
0%
0/1
N/A
0
 
 1  
 /* Copyright (2006-2008) Schibsted Søk AS
 2  
  * This file is part of SESAT.
 3  
  *
 4  
  *   SESAT is free software: you can redistribute it and/or modify
 5  
  *   it under the terms of the GNU Affero General Public License as published by
 6  
  *   the Free Software Foundation, either version 3 of the License, or
 7  
  *   (at your option) any later version.
 8  
  *
 9  
  *   SESAT is distributed in the hope that it will be useful,
 10  
  *   but WITHOUT ANY WARRANTY; without even the implied warranty of
 11  
  *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 12  
  *   GNU Affero General Public License for more details.
 13  
  *
 14  
  *   You should have received a copy of the GNU Affero General Public License
 15  
  *   along with SESAT.  If not, see <http://www.gnu.org/licenses/>.
 16  
 
 17  
  * SyndicationGenerator.java
 18  
  *
 19  
  * Created on June 7, 2006, 2:39 PM
 20  
  */
 21  
 
 22  
 package no.sesat.search.view.output;
 23  
 
 24  
 import com.sun.syndication.feed.synd.SyndContent;
 25  
 import com.sun.syndication.feed.synd.SyndContentImpl;
 26  
 import com.sun.syndication.feed.synd.SyndEnclosure;
 27  
 import com.sun.syndication.feed.synd.SyndEnclosureImpl;
 28  
 import com.sun.syndication.feed.synd.SyndEntry;
 29  
 import com.sun.syndication.feed.synd.SyndEntryImpl;
 30  
 import com.sun.syndication.feed.synd.SyndFeed;
 31  
 import com.sun.syndication.feed.synd.SyndFeedImpl;
 32  
 import com.sun.syndication.io.FeedException;
 33  
 import com.sun.syndication.io.SyndFeedOutput;
 34  
 import no.sesat.search.InfrastructureException;
 35  
 import no.sesat.search.datamodel.DataModelContext;
 36  
 import no.sesat.search.datamodel.generic.StringDataObject;
 37  
 import no.sesat.search.result.ResultItem;
 38  
 import no.sesat.search.result.ResultList;
 39  
 import no.sesat.search.site.Site;
 40  
 import no.sesat.search.site.SiteContext;
 41  
 import no.sesat.search.site.config.PropertiesLoader;
 42  
 import no.sesat.search.site.config.ResourceContext;
 43  
 import no.sesat.search.view.config.SearchTab;
 44  
 import no.sesat.search.site.config.TextMessages;
 45  
 import no.sesat.search.view.output.syndication.modules.SearchResultModule;
 46  
 import no.sesat.search.view.output.syndication.modules.SearchResultModuleImpl;
 47  
 import no.sesat.search.view.velocity.VelocityEngineFactory;
 48  
 import org.apache.commons.lang.StringEscapeUtils;
 49  
 import org.apache.log4j.Logger;
 50  
 import org.apache.velocity.Template;
 51  
 import org.apache.velocity.VelocityContext;
 52  
 import org.apache.velocity.app.VelocityEngine;
 53  
 import org.apache.velocity.exception.MethodInvocationException;
 54  
 import org.apache.velocity.exception.ParseErrorException;
 55  
 import org.apache.velocity.exception.ResourceNotFoundException;
 56  
 
 57  
 import java.io.StringWriter;
 58  
 import java.text.DateFormat;
 59  
 import java.text.ParseException;
 60  
 import java.text.SimpleDateFormat;
 61  
 import java.util.ArrayList;
 62  
 import java.util.Date;
 63  
 import java.util.List;
 64  
 import java.util.Properties;
 65  
 import java.util.TimeZone;
 66  
 import javax.resource.NotSupportedException;
 67  
 import no.sesat.search.result.BasicResultList;
 68  
 
 69  
 /**
 70  
  * Used by the rssDecorator.jsp to print out the results in rss format.
 71  
  *
 72  
  *
 73  
  */
 74  0
 public final class SyndicationGenerator {
 75  
 
 76  
     /**
 77  
      * The context this class needs to do its job.
 78  
      */
 79  
     public interface Context extends SiteContext, DataModelContext, ResourceContext {
 80  
         /**
 81  
          * The tab to generate rss for.
 82  
          *
 83  
          * @return The search tab to generate rss for.
 84  
          */
 85  
         SearchTab getTab();
 86  
 
 87  
         /**
 88  
          * The complete URL of the original page the rss represents.
 89  
          *
 90  
          * @return the url of the original page.
 91  
          */
 92  
         String getURL();
 93  
     }
 94  
 
 95  
     // Constants -----------------------------------------------------
 96  
 
 97  
     // Any other way to get rid of the dc:date tags that ROME generates.
 98  0
     private static final Logger LOG = Logger.getLogger(SyndicationGenerator.class);
 99  
 
 100  
     private static final String DCDATE_PATTERN = "<dc:date>[^<]+</dc:date>";
 101  
 
 102  
     private static final String DEFAULT_DATE_FORMAT = "yyyy-MM-dd'T'HH:mm:ss'Z'";
 103  
     private static final String ERR_TEMPLATE_NOT_FOUND = " Unable to find template for rss field: ";
 104  
     private static final String ERR_TEMPLATE_ERR = " Parse error in template: ";
 105  
     private static final String DEBUG_USING_DEFAULT_DATE_FORMAT = "Using default date format";
 106  
 
 107  
     // Attributes ----------------------------------------------------
 108  
 
 109  
     private final Context context;
 110  
 
 111  
     private final ResultList<ResultItem> result;
 112  
     private final Site site;
 113  
     private final TextMessages text;
 114  0
     private String feedType = "rss_2.0";
 115  
     private final String templateDir;
 116  
     private final VelocityEngine engine;
 117  
     private final String uri;
 118  
     //private final Channels channels;
 119  0
     private String encoding = "UTF-8";
 120  
     private String nowStringUTC;
 121  
 
 122  
     // Static --------------------------------------------------------
 123  
 
 124  
     // Constructors --------------------------------------------------
 125  
 
 126  
     /**
 127  
      * Creates a new instance.
 128  
      *
 129  
      * @param context The context this class needs to do its work.
 130  
      * @throws SyndicationNotSupportedException
 131  
      */
 132  0
     public SyndicationGenerator(final Context context) throws SyndicationNotSupportedException{
 133  
 
 134  0
         if(null == context.getTab().getRssResultName()){ throw new SyndicationNotSupportedException(); }
 135  
 
 136  0
         this.context = context;
 137  
 
 138  0
         this.result = null != context.getDataModel().getSearch(context.getTab().getRssResultName())
 139  
                 ? context.getDataModel().getSearch(context.getTab().getRssResultName()).getResults()
 140  
                 : new BasicResultList<ResultItem>();
 141  
 
 142  0
         this.site = context.getSite();
 143  
 
 144  0
         this.text = TextMessages.valueOf(getTextMessagesContext());
 145  0
         this.uri = context.getURL();
 146  
 
 147  0
         final String type = getParameter("feedType");
 148  
 
 149  0
         if (! "".equals(type)) {
 150  0
             this.feedType = type;
 151  
         }
 152  
 
 153  0
         final String enc = getParameter("encoding");
 154  0
         if (! "".equals(enc)) {
 155  0
             if (encoding.equalsIgnoreCase("iso-8859-1")) {
 156  0
                 this.encoding = "iso-8859-1";
 157  
             }
 158  
         }
 159  
 
 160  0
         templateDir = "rss/" + context.getTab().getId() + "/";
 161  
 
 162  0
         engine = VelocityEngineFactory.valueOf(site).getEngine();
 163  0
     }
 164  
 
 165  
     // Public --------------------------------------------------------
 166  
 
 167  
     /**
 168  
      * Returns the generated rss content.
 169  
      *
 170  
      * @return the rss document.
 171  
      */
 172  
 
 173  
     public String generate() {
 174  
 
 175  0
         String dfString = DEFAULT_DATE_FORMAT;
 176  
 
 177  
         try {
 178  0
             dfString = render("dateFormat_publishedDate", null, 0);
 179  0
         } catch (ResourceNotFoundException ex) {
 180  0
             LOG.trace(DEBUG_USING_DEFAULT_DATE_FORMAT);
 181  0
         }
 182  
 
 183  0
         final DateFormat df = new SimpleDateFormat(dfString);
 184  
 
 185  
         // Zulu time is UTC. But java doesn't know that.
 186  0
         if (dfString.endsWith("'Z'")) {
 187  0
             df.setTimeZone(TimeZone.getTimeZone("UTC"));
 188  
         }
 189  
 
 190  0
         nowStringUTC = df.format(new Date());
 191  
 
 192  
         try {
 193  0
             final SyndFeed feed = new SyndFeedImpl();
 194  0
             final SearchResultModule m = new SearchResultModuleImpl();
 195  
 
 196  0
             m.setNumberOfHits(Integer.toString(result.getHitCount()));
 197  
 
 198  0
             final List<SearchResultModule> modules = new ArrayList<SearchResultModule>();
 199  
 
 200  0
             modules.add(m);
 201  
 
 202  0
             feed.setModules(modules);
 203  
 
 204  0
             feed.setEncoding(this.encoding);
 205  0
             feed.setFeedType(feedType);
 206  0
             feed.setDescription(StringEscapeUtils.unescapeXml(render("description", null, 0)));
 207  0
             feed.setTitle(StringEscapeUtils.unescapeXml(render("title", null, 0)));
 208  0
             feed.setPublishedDate(new Date());
 209  0
             feed.setLink(render("link", null, 0));
 210  
 
 211  0
             final List<SyndEntry> entries = new ArrayList<SyndEntry>();
 212  
 
 213  0
             int idx = 0;
 214  0
             for (ResultItem item : result.getResults()) {
 215  0
                 ++idx;
 216  
 
 217  0
                 final SyndEntry entry = new SyndEntryImpl();
 218  
 
 219  0
                 final SearchResultModule entryModule = new SearchResultModuleImpl();
 220  
 
 221  0
                 if (item.getField("age") != null && !"".equals(item.getField("age"))) {
 222  0
                     entryModule.setArticleAge(item.getField("age"));
 223  
                 }
 224  
 
 225  0
                 if (item.getField("newssource") != null && !"".equals(item.getField("newssource"))) {
 226  0
                     entryModule.setNewsSource(item.getField("newssource"));
 227  
                 }
 228  
 
 229  0
                 final List<SearchResultModule> sModules = new ArrayList<SearchResultModule>();
 230  0
                 sModules.add(entryModule);
 231  0
                 entry.setModules(sModules);
 232  0
                 final SyndContent content = new SyndContentImpl();
 233  
 
 234  0
                 content.setType("text/html");
 235  0
                 final String entryDescription = render("entryDescription", item, idx);
 236  
 
 237  0
                 content.setValue(StringEscapeUtils.unescapeHtml(entryDescription));
 238  
 
 239  0
                 final String publishedDate = render("entryPublishedDate", item, idx);
 240  
 
 241  
                 try {
 242  0
                     final Date date = df.parse(publishedDate);
 243  
 
 244  0
                     if (date.getTime() > 0) {
 245  0
                         entry.setPublishedDate(df.parse(publishedDate));
 246  
                     } else {
 247  0
                         LOG.debug("Publish date set to epoch. Ignoring");
 248  
                     }
 249  0
                 } catch (ParseException ex) {
 250  0
                     if (!(publishedDate == null || publishedDate.trim().equals(""))) {
 251  0
                         LOG.error("Cannot parse " + publishedDate + " using df " + dfString);
 252  
                     } else {
 253  0
                         LOG.debug("Publish date is empty. Using current time");
 254  
                     }
 255  
 
 256  0
                     entry.setPublishedDate(new Date());
 257  0
                 }
 258  
 
 259  0
                 entry.setTitle(render("entryTitle", item, idx));
 260  0
                 entry.setLink(render("entryUri", item, idx));
 261  
 
 262  
 
 263  
                 try {
 264  0
                     final SyndEnclosure enclosure = new SyndEnclosureImpl();
 265  
 
 266  0
                     enclosure.setUrl(render("entryEnclosure", item, idx));
 267  
 
 268  0
                     final List<SyndEnclosure> enclosures = new ArrayList<SyndEnclosure>();
 269  0
                     enclosures.add(enclosure);
 270  0
                     entry.setEnclosures(enclosures);
 271  
 
 272  
                     // @todo. specific to sesam.no. put somewhere else...
 273  0
                     if ("swip".equals(context.getTab().getKey())) {
 274  0
                         enclosure.setType("image/gif");
 275  
                     } else {
 276  0
                         enclosure.setType("image/png");
 277  
                     }
 278  0
                 } catch (ResourceNotFoundException ex) {
 279  0
                     LOG.debug("Template for enclosure not found. Skipping.");
 280  0
                 }
 281  
 
 282  
 
 283  0
                 final List<SyndContent> contents = new ArrayList<SyndContent>();
 284  
 
 285  0
                 contents.add(content);
 286  
 
 287  0
                 entry.setContents(contents);
 288  0
                 entry.setDescription(content);
 289  
 
 290  0
                 entries.add(entry);
 291  0
             }
 292  
 
 293  0
             feed.setEntries(entries);
 294  
 
 295  0
             final SyndFeedOutput output = new SyndFeedOutput();
 296  0
             return output.outputString(feed).replaceAll(DCDATE_PATTERN, "");
 297  0
         } catch (ResourceNotFoundException ex) {
 298  0
             throw new RuntimeException(ex);
 299  0
         } catch (FeedException ex) {
 300  0
             throw new RuntimeException(ex);
 301  
         }
 302  
     }
 303  
 
 304  
     // Package protected ---------------------------------------------
 305  
 
 306  
     // Protected -----------------------------------------------------
 307  
 
 308  
     // Private -------------------------------------------------------
 309  
 
 310  
     private String render(
 311  
             final String name,
 312  
             final ResultItem item,
 313  
             final int itemIdx) throws ResourceNotFoundException {
 314  
 
 315  0
         final String templateUri = templateDir + name;
 316  
 
 317  
         try {
 318  0
             final VelocityContext cxt = VelocityEngineFactory.newContextInstance();
 319  
 
 320  0
             cxt.put("text", text);
 321  0
             cxt.put("now", nowStringUTC);
 322  
 
 323  0
             if (item != null) {
 324  0
                 cxt.put("item", item);
 325  0
                 cxt.put("itemIdx", itemIdx);
 326  
             }
 327  
 
 328  0
             cxt.put("datamodel", context.getDataModel());
 329  
 
 330  0
             final String origUri = uri.replaceAll("&?layout=[^&]+", "").replaceAll("&?feedtype=[^&]+", "");
 331  0
             cxt.put("uri", origUri);
 332  
 
 333  0
             final Template tpl = VelocityEngineFactory.getTemplate(engine, site, templateUri);
 334  
 
 335  0
             final StringWriter writer = new StringWriter();
 336  0
             tpl.merge(cxt, writer);
 337  
 
 338  0
             return writer.toString();
 339  
 
 340  0
         } catch (ParseErrorException ex) {
 341  0
             LOG.error(ERR_TEMPLATE_ERR + templateUri);
 342  0
             throw new InfrastructureException(ex);
 343  
 
 344  0
         } catch (MethodInvocationException ex) {
 345  0
             throw new InfrastructureException(ex);
 346  
 
 347  0
         } catch (ResourceNotFoundException ex) {
 348  0
             LOG.debug(ERR_TEMPLATE_NOT_FOUND + templateUri);
 349  0
             throw ex;
 350  
 
 351  0
         } catch (Exception ex) {
 352  0
             throw new InfrastructureException(ex);
 353  
         }
 354  
     }
 355  
 
 356  
     private String getParameter(final String parameterName) {
 357  0
         final StringDataObject value = context.getDataModel().getParameters().getValue(parameterName);
 358  
 
 359  0
         if (value != null) {
 360  0
             return value.toString();
 361  
         } else {
 362  0
             return "";
 363  
         }
 364  
     }
 365  
 
 366  
     private TextMessages.Context getTextMessagesContext() {
 367  0
         return new TextMessages.Context() {
 368  
             public Site getSite() {
 369  0
                 return context.getSite();
 370  
             }
 371  
 
 372  
             public PropertiesLoader newPropertiesLoader(
 373  
                     final SiteContext siteCxt,
 374  
                     final String resource,
 375  
                     final Properties properties) {
 376  0
             return context.newPropertiesLoader(siteCxt, resource, properties);
 377  
             }
 378  
         };
 379  
     }
 380  
 
 381  
 //    private Channels.Context getChannelContext() {
 382  
 //        return new Channels.Context() {
 383  
 //            public Site getSite() {
 384  
 //                return context.getSite();
 385  
 //            }
 386  
 //            public DocumentLoader newDocumentLoader(
 387  
 //                    final SiteContext cxt,
 388  
 //                    final String resource,
 389  
 //                    final DocumentBuilder builder) {
 390  
 //                return context.newDocumentLoader(cxt, resource, builder);
 391  
 //            }
 392  
 //        };
 393  
 //    }
 394  
 
 395  
     // Inner classes -------------------------------------------------
 396  
 
 397  0
     public static final class SyndicationNotSupportedException extends Exception{
 398  
 
 399  
     }
 400  
 }