001/* 002 * Copyright 2015 Anyware Services 003 * 004 * Licensed under the Apache License, Version 2.0 (the "License"); 005 * you may not use this file except in compliance with the License. 006 * You may obtain a copy of the License at 007 * 008 * http://www.apache.org/licenses/LICENSE-2.0 009 * 010 * Unless required by applicable law or agreed to in writing, software 011 * distributed under the License is distributed on an "AS IS" BASIS, 012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 013 * See the License for the specific language governing permissions and 014 * limitations under the License. 015 */ 016package org.ametys.plugins.syndication; 017 018import java.io.IOException; 019import java.text.SimpleDateFormat; 020import java.util.Date; 021import java.util.Iterator; 022import java.util.List; 023import java.util.Map; 024 025import org.apache.avalon.framework.service.ServiceException; 026import org.apache.avalon.framework.service.ServiceManager; 027import org.apache.cocoon.ProcessingException; 028import org.apache.cocoon.environment.ObjectModelHelper; 029import org.apache.cocoon.environment.Request; 030import org.apache.cocoon.generation.ServiceableGenerator; 031import org.apache.cocoon.xml.AttributesImpl; 032import org.apache.cocoon.xml.XMLUtils; 033import org.apache.commons.lang.StringUtils; 034import org.xml.sax.SAXException; 035 036import org.ametys.runtime.config.Config; 037import org.ametys.web.renderingcontext.RenderingContext; 038import org.ametys.web.renderingcontext.RenderingContextHandler; 039import org.ametys.web.repository.page.Page; 040import org.ametys.web.repository.site.Site; 041 042import com.rometools.modules.mediarss.MediaEntryModule; 043import com.rometools.modules.mediarss.MediaModule; 044import com.rometools.modules.mediarss.types.MediaContent; 045import com.rometools.modules.mediarss.types.MediaGroup; 046import com.rometools.modules.mediarss.types.Metadata; 047import com.rometools.modules.mediarss.types.Reference; 048import com.rometools.modules.mediarss.types.Thumbnail; 049import com.rometools.modules.mediarss.types.UrlReference; 050import com.rometools.rome.feed.synd.SyndCategory; 051import com.rometools.rome.feed.synd.SyndContent; 052import com.rometools.rome.feed.synd.SyndEnclosure; 053import com.rometools.rome.feed.synd.SyndEntry; 054import com.rometools.rome.feed.synd.SyndFeed; 055import com.rometools.rome.feed.synd.SyndImage; 056import com.rometools.rome.feed.synd.SyndPerson; 057 058/** 059 * Generates the contents of an external RSS of Atom feed.<br> 060 * This implementation is based on the ROME API. 061 */ 062public class FeedGenerator extends ServiceableGenerator 063{ 064 /** The feed cache */ 065 protected FeedCache _feedCache; 066 067 /** The rendering context handler. */ 068 protected RenderingContextHandler _renderingContext; 069 070 @Override 071 public void service(ServiceManager serviceManager) throws ServiceException 072 { 073 super.service(serviceManager); 074 _feedCache = (FeedCache) serviceManager.lookup(FeedCache.ROLE); 075 _renderingContext = (RenderingContextHandler) serviceManager.lookup(RenderingContextHandler.ROLE); 076 } 077 078 @Override 079 public void generate() throws IOException, SAXException, ProcessingException 080 { 081 boolean isCustom = false; 082 boolean isSelected = false; 083 long length = -1; 084 String url; 085 String name = ""; 086 int lifeTime = 0; 087 088 @SuppressWarnings("unchecked") 089 Map<String, Object> ctxParameters = (Map<String, Object>) objectModel.get(ObjectModelHelper.PARENT_CONTEXT); 090 if (ctxParameters != null) 091 { 092 url = (String) ctxParameters.get("url"); 093 name = (String) ctxParameters.get("name"); 094 lifeTime = (Integer) ctxParameters.get("cache"); 095 length = (Long) ctxParameters.get("length"); 096 isCustom = (Boolean) ctxParameters.get("isCustom"); 097 isSelected = (Boolean) ctxParameters.get("isSelected"); 098 } 099 else 100 { 101 url = source; 102 } 103 104 contentHandler.startDocument(); 105 106 if (_checkForInfiniteLoop(url)) 107 { 108 getLogger().error("Infinite loop detected for RSS feed : " + url + ". The RSS feed url can not be the page itself."); 109 if (isCustom && _renderingContext.getRenderingContext() == RenderingContext.FRONT) 110 { 111 _saxErrorFeed("feed-error-custom", url, name, null); 112 } 113 else if (_renderingContext.getRenderingContext() == RenderingContext.BACK) 114 { 115 _saxErrorFeed("feed-error", url, name, "Infinite loop detected for RSS feed : " + url + ". The RSS feed url can not be the page itself."); 116 } 117 } 118 else 119 { 120 FeedResult feedResult = _feedCache.getFeed(url, lifeTime); 121 122 if (feedResult.getStatus() == FeedResult.STATUS_OK) 123 { 124 SyndFeed feed = feedResult.getSynFeed(); 125 _saxFeeds(feed, length, url, isCustom, isSelected); 126 } 127 else if (_renderingContext.getRenderingContext() == RenderingContext.BACK) 128 { 129 _saxErrorFeed("feed-error", url, name, feedResult.getMessageError()); 130 } 131 else if (isCustom && _renderingContext.getRenderingContext() == RenderingContext.FRONT) 132 { 133 _saxErrorFeed("feed-error-custom", url, name, null); 134 } 135 } 136 137 contentHandler.endDocument(); 138 } 139 140 private void _saxErrorFeed (String errorName, String url, String name, String messageError) throws SAXException 141 { 142 AttributesImpl atts = new AttributesImpl(); 143 _addAttribute(atts, "url", url); 144 _addAttribute(atts, "name", name); 145 if (StringUtils.isNotEmpty(messageError)) 146 { 147 _addAttribute(atts, "messageError", messageError); 148 } 149 150 XMLUtils.createElement(contentHandler, errorName, atts); 151 } 152 153 private void _saxFeeds (SyndFeed feed, long length, String url, Boolean isCustom, Boolean isSelected) throws SAXException 154 { 155 AttributesImpl atts = new AttributesImpl(); 156 157 _addAttribute(atts, "title", feed.getTitle()); 158 _addAttribute(atts, "description", feed.getDescription()); 159 _addAttribute(atts, "feedUrl", url); 160 _addAttribute(atts, "isCustom", String.valueOf(isCustom)); 161 _addAttribute(atts, "isSelected", String.valueOf(isSelected)); 162 163 XMLUtils.startElement(contentHandler, "feed", atts); 164 165 SyndImage image = feed.getImage(); 166 167 if (image != null) 168 { 169 atts = new AttributesImpl(); 170 _addAttribute(atts, "link", image.getLink()); 171 _addAttribute(atts, "url", image.getUrl()); 172 _addAttribute(atts, "title", image.getTitle()); 173 174 XMLUtils.createElement(contentHandler, "image", atts); 175 } 176 177 List<SyndCategory> feedCategories = feed.getCategories(); 178 _saxCategories(feedCategories); 179 180 List<SyndEntry> entries = feed.getEntries(); 181 if (entries != null) 182 { 183 int index = 0; 184 Iterator<SyndEntry> it = entries.iterator(); 185 186 while (it.hasNext() && (length == -1 || index < length)) 187 { 188 SyndEntry entry = it.next(); 189 190 atts = new AttributesImpl(); 191 _addAttribute(atts, "title", entry.getTitle()); 192 _addAttribute(atts, "link", entry.getLink()); 193 194 Date date = entry.getPublishedDate(); 195 if (date != null) 196 { 197 _addAttribute(atts, "date", new SimpleDateFormat("yyyy-MM-dd'T'HH:mm").format(date)); 198 } 199 200 XMLUtils.startElement(contentHandler, "entry", atts); 201 202 SyndContent desc = entry.getDescription(); 203 if (desc != null && desc.getValue() != null) 204 { 205 atts = new AttributesImpl(); 206 _addAttribute(atts, "type", desc.getType()); 207 XMLUtils.createElement(contentHandler, "description", atts, desc.getValue()); 208 } 209 210 List<SyndPerson> authors = entry.getAuthors(); 211 if (authors != null) 212 { 213 for (SyndPerson author : authors) 214 { 215 atts = new AttributesImpl(); 216 _addAttribute(atts, "email", author.getEmail()); 217 XMLUtils.createElement(contentHandler, "author", atts, author.getName()); 218 } 219 } 220 221 // Attachments 222 _saxEnclosures (entry); 223 224 List<SyndCategory> entryCategories = entry.getCategories(); 225 _saxCategories(entryCategories); 226 227 _saxMediaRSS(entry); 228 229 XMLUtils.endElement(contentHandler, "entry"); 230 231 index++; 232 } 233 } 234 235 XMLUtils.endElement(contentHandler, "feed"); 236 } 237 238 private void _saxCategories(List<SyndCategory> feedCategories) throws SAXException 239 { 240 for (SyndCategory category : feedCategories) 241 { 242 AttributesImpl categoryAtts = new AttributesImpl(); 243 String taxonomyUri = category.getTaxonomyUri(); 244 if (taxonomyUri != null) 245 { 246 _addAttribute(categoryAtts, "domain", taxonomyUri); 247 } 248 XMLUtils.createElement(contentHandler, "category", categoryAtts, category.getName()); 249 } 250 } 251 252 private void _saxEnclosures (SyndEntry entry) throws SAXException 253 { 254 List<SyndEnclosure> enclosures = entry.getEnclosures(); 255 if (enclosures != null) 256 { 257 for (SyndEnclosure enclosure : enclosures) 258 { 259 AttributesImpl atts = new AttributesImpl(); 260 _addAttribute(atts, "type", enclosure.getType()); 261 _addAttribute(atts, "url", enclosure.getUrl()); 262 _addAttribute(atts, "name", _enclosureName(enclosure.getUrl())); 263 XMLUtils.startElement(contentHandler, "enclosure", atts); 264 _saxLength(enclosure.getLength()); 265 XMLUtils.endElement(contentHandler, "enclosure"); 266 } 267 } 268 } 269 270 private void _saxMediaRSS(SyndEntry entry) throws SAXException 271 { 272 MediaEntryModule module = (MediaEntryModule) entry.getModule(MediaModule.URI); 273 if (module != null) 274 { 275 XMLUtils.startElement(contentHandler, "media"); 276 277 for (MediaContent content : module.getMediaContents()) 278 { 279 _saxMediaContent(content, false); 280 } 281 282 for (MediaGroup group : module.getMediaGroups()) 283 { 284 AttributesImpl atts = new AttributesImpl(); 285 _addAttribute(atts, "defaultContentIndex", group.getDefaultContentIndex()); 286 XMLUtils.startElement(contentHandler, "mediagroup", atts); 287 288 for (MediaContent content : group.getContents()) 289 { 290 _saxMediaContent(content, true); 291 } 292 293 XMLUtils.endElement(contentHandler, "mediagroup"); 294 } 295 296 Metadata metadata = module.getMetadata(); 297 298 if (metadata != null) 299 { 300 saxMediaMetadata(metadata); 301 } 302 303 XMLUtils.endElement(contentHandler, "media"); 304 } 305 } 306 307 private void _saxMediaContent(MediaContent content, boolean inGroup) throws SAXException 308 { 309 Metadata metadata = content.getMetadata(); 310 Reference reference = content.getReference(); 311 312 AttributesImpl atts = new AttributesImpl(); 313 314 if (reference instanceof UrlReference) 315 { 316 _addAttribute(atts, "url", ((UrlReference) reference).getUrl().toString()); 317 } 318 if (inGroup && content.isDefaultContent()) 319 { 320 atts.addCDATAAttribute("default", "true"); 321 } 322 _addAttribute(atts, "type", content.getType()); 323 _addAttribute(atts, "medium", content.getMedium()); 324 _addAttribute(atts, "size", content.getFileSize()); 325 _addAttribute(atts, "width", content.getWidth()); 326 _addAttribute(atts, "height", content.getHeight()); 327 328 XMLUtils.startElement(contentHandler, "mediacontent", atts); 329 330 if (metadata != null) 331 { 332 saxMediaMetadata(metadata); 333 } 334 335 XMLUtils.endElement(contentHandler, "mediacontent"); 336 } 337 338 private void saxMediaMetadata(Metadata metadata) throws SAXException 339 { 340 if (metadata != null) 341 { 342 String title = metadata.getTitle(); 343 Thumbnail[] thumbnails = metadata.getThumbnail(); 344 345 if (title != null) 346 { 347 AttributesImpl titleAttrs = new AttributesImpl(); 348 _addAttribute(titleAttrs, "type", metadata.getTitleType()); 349 XMLUtils.createElement(contentHandler, "title", titleAttrs, title); 350 } 351 352 for (Thumbnail thumbnail : thumbnails) 353 { 354 AttributesImpl thumbAttrs = new AttributesImpl(); 355 _addAttribute(thumbAttrs, "url", thumbnail.getUrl().toString()); 356 _addAttribute(thumbAttrs, "width", thumbnail.getWidth()); 357 _addAttribute(thumbAttrs, "height", thumbnail.getHeight()); 358 XMLUtils.createElement(contentHandler, "thumbnail", thumbAttrs); 359 } 360 } 361 } 362 363 private boolean _checkForInfiniteLoop (String src) 364 { 365 Request request = ObjectModelHelper.getRequest(objectModel); 366 Page page = (Page) request.getAttribute(Page.class.getName()); 367 368 if (page == null) 369 { 370 return false; 371 } 372 373 String cmsUrl = StringUtils.stripEnd(StringUtils.removeEndIgnoreCase(Config.getInstance().getValue("cms.url"), "index.html"), "/"); 374 Site site = page.getSite(); 375 String siteName = page.getSiteName(); 376 String lang = page.getSitemapName(); 377 String path = page.getPathInSitemap(); 378 379 String frontUrl = site.getUrl() + "/" + lang + "/" + path + ".html"; 380 if (src.equals(frontUrl)) 381 { 382 return true; 383 } 384 385 String previewUrl = cmsUrl + "/preview/" + siteName + "/" + lang + "/" + path + ".html"; 386 if (src.equals(previewUrl)) 387 { 388 return true; 389 } 390 391 String liveUrl = cmsUrl + "/live/" + siteName + "/" + lang + "/" + path + ".html"; 392 if (src.equals(liveUrl)) 393 { 394 return true; 395 } 396 397 String backUrl = cmsUrl + siteName + "/" + lang + "/" + path + ".html"; 398 if (src.equals(backUrl)) 399 { 400 return true; 401 } 402 403 return false; 404 } 405 406 private String _enclosureName (String url) 407 { 408 String name = url; 409 if (name != null && name.lastIndexOf("/") > 0) 410 { 411 name = name.substring(name.lastIndexOf("/") + 1); 412 } 413 414 if (name != null && name.indexOf("?") > 0) 415 { 416 name = name.substring(0, name.indexOf("?")); 417 } 418 419 return name; 420 } 421 422 private void _addAttribute(AttributesImpl atts, String name, Object value) 423 { 424 if (value != null) 425 { 426 if (value instanceof String) 427 { 428 atts.addCDATAAttribute(name, (String) value); 429 } 430 else if (value instanceof Integer) 431 { 432 atts.addCDATAAttribute(name, ((Integer) value).toString()); 433 } 434 else if (value instanceof Long) 435 { 436 atts.addCDATAAttribute(name, ((Long) value).toString()); 437 } 438 else if (value instanceof Double) 439 { 440 atts.addCDATAAttribute(name, ((Double) value).toString()); 441 } 442 else if (value instanceof Float) 443 { 444 atts.addCDATAAttribute(name, ((Float) value).toString()); 445 } 446 } 447 } 448 449 private void _saxLength(long length) throws SAXException 450 { 451 org.ametys.core.util.StringUtils.toReadableDataSize(length).toSAX(contentHandler, "length"); 452 } 453}