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.ArrayList; 021import java.util.Date; 022import java.util.Iterator; 023import java.util.List; 024import java.util.Map; 025 026import org.apache.avalon.framework.service.ServiceException; 027import org.apache.avalon.framework.service.ServiceManager; 028import org.apache.cocoon.ProcessingException; 029import org.apache.cocoon.environment.ObjectModelHelper; 030import org.apache.cocoon.environment.Request; 031import org.apache.cocoon.generation.ServiceableGenerator; 032import org.apache.cocoon.xml.AttributesImpl; 033import org.apache.cocoon.xml.XMLUtils; 034import org.apache.commons.lang.StringUtils; 035import org.xml.sax.SAXException; 036 037import org.ametys.runtime.config.Config; 038import org.ametys.runtime.i18n.I18nizableText; 039import org.ametys.web.renderingcontext.RenderingContext; 040import org.ametys.web.renderingcontext.RenderingContextHandler; 041import org.ametys.web.repository.page.Page; 042import org.ametys.web.repository.site.Site; 043 044import com.rometools.modules.mediarss.MediaEntryModule; 045import com.rometools.modules.mediarss.MediaModule; 046import com.rometools.modules.mediarss.types.MediaContent; 047import com.rometools.modules.mediarss.types.MediaGroup; 048import com.rometools.modules.mediarss.types.Metadata; 049import com.rometools.modules.mediarss.types.Reference; 050import com.rometools.modules.mediarss.types.Thumbnail; 051import com.rometools.modules.mediarss.types.UrlReference; 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 179 List<SyndEntry> entries = feed.getEntries(); 180 if (entries != null) 181 { 182 int index = 0; 183 Iterator<SyndEntry> it = entries.iterator(); 184 185 while (it.hasNext() && (length == -1 || index < length)) 186 { 187 SyndEntry entry = it.next(); 188 189 atts = new AttributesImpl(); 190 _addAttribute(atts, "title", entry.getTitle()); 191 _addAttribute(atts, "link", entry.getLink()); 192 193 Date date = entry.getPublishedDate(); 194 if (date != null) 195 { 196 _addAttribute(atts, "date", new SimpleDateFormat("yyyy-MM-dd'T'HH:mm").format(date)); 197 } 198 199 XMLUtils.startElement(contentHandler, "entry", atts); 200 201 SyndContent desc = entry.getDescription(); 202 if (desc != null && desc.getValue() != null) 203 { 204 atts = new AttributesImpl(); 205 _addAttribute(atts, "type", desc.getType()); 206 XMLUtils.createElement(contentHandler, "description", atts, desc.getValue()); 207 } 208 209 List<SyndPerson> authors = entry.getAuthors(); 210 if (authors != null) 211 { 212 for (SyndPerson author : authors) 213 { 214 atts = new AttributesImpl(); 215 _addAttribute(atts, "email", author.getEmail()); 216 XMLUtils.createElement(contentHandler, "author", atts, author.getName()); 217 } 218 } 219 220 // Attachments 221 _saxEnclosures (entry); 222 223 _saxMediaRSS(entry); 224 225 XMLUtils.endElement(contentHandler, "entry"); 226 227 index++; 228 } 229 } 230 231 XMLUtils.endElement(contentHandler, "feed"); 232 } 233 234 private void _saxEnclosures (SyndEntry entry) throws SAXException 235 { 236 List<SyndEnclosure> enclosures = entry.getEnclosures(); 237 if (enclosures != null) 238 { 239 for (SyndEnclosure enclosure : enclosures) 240 { 241 AttributesImpl atts = new AttributesImpl(); 242 _addAttribute(atts, "type", enclosure.getType()); 243 _addAttribute(atts, "url", enclosure.getUrl()); 244 _addAttribute(atts, "name", _enclosureName(enclosure.getUrl())); 245 XMLUtils.startElement(contentHandler, "enclosure", atts); 246 _saxLength(enclosure.getLength()); 247 XMLUtils.endElement(contentHandler, "enclosure"); 248 } 249 } 250 } 251 252 private void _saxMediaRSS(SyndEntry entry) throws SAXException 253 { 254 MediaEntryModule module = (MediaEntryModule) entry.getModule(MediaModule.URI); 255 if (module != null) 256 { 257 XMLUtils.startElement(contentHandler, "media"); 258 259 for (MediaContent content : module.getMediaContents()) 260 { 261 _saxMediaContent(content, false); 262 } 263 264 for (MediaGroup group : module.getMediaGroups()) 265 { 266 AttributesImpl atts = new AttributesImpl(); 267 _addAttribute(atts, "defaultContentIndex", group.getDefaultContentIndex()); 268 XMLUtils.startElement(contentHandler, "mediagroup", atts); 269 270 for (MediaContent content : group.getContents()) 271 { 272 _saxMediaContent(content, true); 273 } 274 275 XMLUtils.endElement(contentHandler, "mediagroup"); 276 } 277 278 Metadata metadata = module.getMetadata(); 279 280 if (metadata != null) 281 { 282 saxMediaMetadata(metadata); 283 } 284 285 XMLUtils.endElement(contentHandler, "media"); 286 } 287 } 288 289 private void _saxMediaContent(MediaContent content, boolean inGroup) throws SAXException 290 { 291 Metadata metadata = content.getMetadata(); 292 Reference reference = content.getReference(); 293 294 AttributesImpl atts = new AttributesImpl(); 295 296 if (reference instanceof UrlReference) 297 { 298 _addAttribute(atts, "url", ((UrlReference) reference).getUrl().toString()); 299 } 300 if (inGroup && content.isDefaultContent()) 301 { 302 atts.addCDATAAttribute("default", "true"); 303 } 304 _addAttribute(atts, "type", content.getType()); 305 _addAttribute(atts, "medium", content.getMedium()); 306 _addAttribute(atts, "size", content.getFileSize()); 307 _addAttribute(atts, "width", content.getWidth()); 308 _addAttribute(atts, "height", content.getHeight()); 309 310 XMLUtils.startElement(contentHandler, "mediacontent", atts); 311 312 if (metadata != null) 313 { 314 saxMediaMetadata(metadata); 315 } 316 317 XMLUtils.endElement(contentHandler, "mediacontent"); 318 } 319 320 private void saxMediaMetadata(Metadata metadata) throws SAXException 321 { 322 if (metadata != null) 323 { 324 String title = metadata.getTitle(); 325 Thumbnail[] thumbnails = metadata.getThumbnail(); 326 327 if (title != null) 328 { 329 AttributesImpl titleAttrs = new AttributesImpl(); 330 _addAttribute(titleAttrs, "type", metadata.getTitleType()); 331 XMLUtils.createElement(contentHandler, "title", titleAttrs, title); 332 } 333 334 for (Thumbnail thumbnail : thumbnails) 335 { 336 AttributesImpl thumbAttrs = new AttributesImpl(); 337 _addAttribute(thumbAttrs, "url", thumbnail.getUrl().toString()); 338 _addAttribute(thumbAttrs, "width", thumbnail.getWidth()); 339 _addAttribute(thumbAttrs, "height", thumbnail.getHeight()); 340 XMLUtils.createElement(contentHandler, "thumbnail", thumbAttrs); 341 } 342 } 343 } 344 345 private boolean _checkForInfiniteLoop (String src) 346 { 347 Request request = ObjectModelHelper.getRequest(objectModel); 348 Page page = (Page) request.getAttribute(Page.class.getName()); 349 350 if (page == null) 351 { 352 return false; 353 } 354 355 String cmsUrl = StringUtils.stripEnd(StringUtils.removeEndIgnoreCase(Config.getInstance().getValue("cms.url"), "index.html"), "/"); 356 Site site = page.getSite(); 357 String siteName = page.getSiteName(); 358 String lang = page.getSitemapName(); 359 String path = page.getPathInSitemap(); 360 361 String frontUrl = site.getUrl() + "/" + lang + "/" + path + ".html"; 362 if (src.equals(frontUrl)) 363 { 364 return true; 365 } 366 367 String previewUrl = cmsUrl + "/preview/" + siteName + "/" + lang + "/" + path + ".html"; 368 if (src.equals(previewUrl)) 369 { 370 return true; 371 } 372 373 String liveUrl = cmsUrl + "/live/" + siteName + "/" + lang + "/" + path + ".html"; 374 if (src.equals(liveUrl)) 375 { 376 return true; 377 } 378 379 String backUrl = cmsUrl + siteName + "/" + lang + "/" + path + ".html"; 380 if (src.equals(backUrl)) 381 { 382 return true; 383 } 384 385 return false; 386 } 387 388 private String _enclosureName (String url) 389 { 390 String name = url; 391 if (name != null && name.lastIndexOf("/") > 0) 392 { 393 name = name.substring(name.lastIndexOf("/") + 1); 394 } 395 396 if (name != null && name.indexOf("?") > 0) 397 { 398 name = name.substring(0, name.indexOf("?")); 399 } 400 401 return name; 402 } 403 404 private void _addAttribute(AttributesImpl atts, String name, Object value) 405 { 406 if (value != null) 407 { 408 if (value instanceof String) 409 { 410 atts.addCDATAAttribute(name, (String) value); 411 } 412 else if (value instanceof Integer) 413 { 414 atts.addCDATAAttribute(name, ((Integer) value).toString()); 415 } 416 else if (value instanceof Long) 417 { 418 atts.addCDATAAttribute(name, ((Long) value).toString()); 419 } 420 else if (value instanceof Double) 421 { 422 atts.addCDATAAttribute(name, ((Double) value).toString()); 423 } 424 else if (value instanceof Float) 425 { 426 atts.addCDATAAttribute(name, ((Float) value).toString()); 427 } 428 } 429 } 430 431 private void _saxLength(long length) throws SAXException 432 { 433 List<String> params = new ArrayList<>(); 434 if (length < 1024) 435 { 436 // Bytes 437 params.add(String.valueOf(length)); 438 new I18nizableText("plugin.syndication", "ENCLOSURE_LENGTH_BYTES", params).toSAX(contentHandler, "length"); 439 } 440 else if (length < 1024 * 1024) 441 { 442 // Kb 443 params.add(String.valueOf(Math.round(((length * 10) / 1024) / 10))); 444 new I18nizableText("plugin.syndication", "ENCLOSURE_LENGTH_KB", params).toSAX(contentHandler, "length"); 445 } 446 else if (length < 1024 * 1024 * 1024) 447 { 448 // Mb 449 params.add(String.valueOf(Math.round(((length * 10) / (1024 * 1024)) / 10))); 450 new I18nizableText("plugin.syndication", "ENCLOSURE_LENGTH_MB", params).toSAX(contentHandler, "length"); 451 } 452 else 453 { 454 // Gb 455 params.add(String.valueOf(Math.round(((length * 10) / (1024 * 1024 * 1024)) / 10))); 456 new I18nizableText("plugin.syndication", "ENCLOSURE_LENGTH_GB", params).toSAX(contentHandler, "length"); 457 } 458 } 459}