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