001/* 002 * Copyright 2010 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.newsletter.generators; 017 018import java.io.IOException; 019import java.time.LocalDate; 020import java.time.ZoneId; 021import java.util.Date; 022import java.util.Iterator; 023 024import org.apache.avalon.framework.service.ServiceException; 025import org.apache.avalon.framework.service.ServiceManager; 026import org.apache.cocoon.ProcessingException; 027import org.apache.cocoon.components.source.impl.SitemapSource; 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.apache.excalibur.source.SourceResolver; 035import org.xml.sax.SAXException; 036 037import org.ametys.cms.repository.Content; 038import org.ametys.cms.repository.ContentTypeExpression; 039import org.ametys.cms.repository.DefaultContent; 040import org.ametys.core.util.DateUtils; 041import org.ametys.core.util.FilenameUtils; 042import org.ametys.core.util.IgnoreRootHandler; 043import org.ametys.core.util.URIUtils; 044import org.ametys.plugins.newsletter.category.Category; 045import org.ametys.plugins.newsletter.category.CategoryProviderExtensionPoint; 046import org.ametys.plugins.repository.AmetysObjectIterable; 047import org.ametys.plugins.repository.AmetysObjectResolver; 048import org.ametys.plugins.repository.provider.RequestAttributeWorkspaceSelector; 049import org.ametys.plugins.repository.query.SortCriteria; 050import org.ametys.plugins.repository.query.expression.AndExpression; 051import org.ametys.plugins.repository.query.expression.Expression; 052import org.ametys.plugins.repository.query.expression.Expression.Operator; 053import org.ametys.plugins.repository.query.expression.MetadataExpression; 054import org.ametys.plugins.repository.query.expression.StringExpression; 055import org.ametys.runtime.model.type.ElementType; 056import org.ametys.web.WebConstants; 057 058/** 059 * SAX the newsletters of a given category 060 * 061 */ 062public class NewsletterListGenerator extends ServiceableGenerator 063{ 064 065 /** The category provider extension point. */ 066 protected CategoryProviderExtensionPoint _categoryProviderEP; 067 068 /** The excalibur source resolver. */ 069 protected SourceResolver _sourceResolver; 070 071 private AmetysObjectResolver _resolver; 072 073 @Override 074 public void service(ServiceManager smanager) throws ServiceException 075 { 076 super.service(smanager); 077 _resolver = (AmetysObjectResolver) smanager.lookup(AmetysObjectResolver.ROLE); 078 _sourceResolver = (SourceResolver) smanager.lookup(SourceResolver.ROLE); 079 _categoryProviderEP = (CategoryProviderExtensionPoint) smanager.lookup(CategoryProviderExtensionPoint.ROLE); 080 } 081 082 @Override 083 public void generate() throws IOException, SAXException, ProcessingException 084 { 085 Request request = ObjectModelHelper.getRequest(objectModel); 086 String workspaceName = RequestAttributeWorkspaceSelector.getForcedWorkspace(request); 087 088 // Force workspace live for search 089 RequestAttributeWorkspaceSelector.setForcedWorkspace(request, WebConstants.LIVE_WORKSPACE); 090 091 String siteName = parameters.getParameter("siteName", (String) request.getAttribute(WebConstants.REQUEST_ATTR_SITE_NAME)); 092 String categoryId = parameters.getParameter("category", null); 093 String encodedCategoryId = parameters.getParameter("encodedCategoryId", null); 094 boolean saxNewsletterContent = parameters.getParameterAsBoolean("newsletterContent", false); 095 String header = parameters.getParameter("header", ""); 096 int length = parameters.getParameterAsInteger("length", 0); 097 boolean displayRss = parameters.getParameterAsBoolean("rss", false); 098 099 if (categoryId == null && StringUtils.isNotEmpty(encodedCategoryId)) 100 { 101 categoryId = URIUtils.decode(encodedCategoryId); 102 } 103 104 Expression cTypeExpr = new ContentTypeExpression(Operator.EQ, "org.ametys.plugins.newsletter.Content.newsletter"); 105 Expression validExpr = new MetadataExpression(DefaultContent.METADATA_LAST_VALIDATION); 106 Expression siteExpr = new StringExpression("site", Operator.EQ, siteName); 107 Expression categoryExpr = new StringExpression("category", Operator.EQ, categoryId); 108 109 Expression expression = new AndExpression(cTypeExpr, validExpr, siteExpr, categoryExpr); 110 111 SortCriteria sortCriteria = new SortCriteria(); 112 sortCriteria.addCriterion(DefaultContent.METADATA_LAST_VALIDATION, false, false); 113 String xpathQuery = org.ametys.plugins.repository.query.QueryHelper.getXPathQuery(null, "ametys:content", expression, sortCriteria); 114 115 try (AmetysObjectIterable<Content> contents = _resolver.query(xpathQuery);) 116 { 117 AttributesImpl attrs = new AttributesImpl(); 118 if (StringUtils.isNotEmpty(header)) 119 { 120 attrs.addCDATAAttribute("service-title", header); 121 } 122 123 Category category = categoryId != null ? _categoryProviderEP.getCategory(categoryId) : null; 124 125 contentHandler.startDocument(); 126 XMLUtils.startElement(contentHandler, "Newsletters", attrs); 127 128 if (category != null) 129 { 130 _saxCategory(category); 131 } 132 133 int index = 0; 134 Iterator<Content> it = contents.iterator(); 135 while (it.hasNext() && index < length) 136 { 137 Content content = it.next(); 138 _saxNewsletter(content, saxNewsletterContent); 139 index++; 140 } 141 142 if (displayRss && category != null) 143 { 144 _saxRssFeed(category); 145 } 146 147 XMLUtils.endElement(contentHandler, "Newsletters"); 148 contentHandler.endDocument(); 149 150 RequestAttributeWorkspaceSelector.setForcedWorkspace(request, workspaceName); 151 } 152 } 153 154 /** 155 * Sax a category. 156 * @param category the category to sax 157 * @throws SAXException if an error occurred while saxing 158 */ 159 protected void _saxCategory(Category category) throws SAXException 160 { 161 AttributesImpl attrs = new AttributesImpl(); 162 attrs.addCDATAAttribute("id", category.getId()); 163 164 XMLUtils.startElement(contentHandler, "category", attrs); 165 166 category.getTitle().toSAX(contentHandler, "title"); 167 category.getDescription().toSAX(contentHandler, "description"); 168 169 XMLUtils.endElement(contentHandler, "category"); 170 } 171 172 private void _saxNewsletter (Content content, boolean saxContent) throws SAXException, IOException 173 { 174 AttributesImpl attr = new AttributesImpl(); 175 attr.addAttribute("", "id", "id", "CDATA", content.getId()); 176 attr.addAttribute("", "name", "name", "CDATA", content.getName()); 177 XMLUtils.startElement(contentHandler, "newsletter", attr); 178 179 XMLUtils.createElement(contentHandler, "title", content.getTitle()); 180 181 long number = content.getValue("newsletter-number", false, 0L); 182 if (number != 0) 183 { 184 XMLUtils.createElement(contentHandler, "number", String.valueOf(number)); 185 } 186 187 LocalDate defaultValue = DateUtils.asLocalDate(content.getLastMajorValidationDate()); 188 LocalDate date = content.getValue("newsletter-date", false, defaultValue); 189 @SuppressWarnings("unchecked") 190 ElementType<LocalDate> dateType = (ElementType<LocalDate>) content.getType("newsletter-date"); 191 192 AttributesImpl dateAttrs = new AttributesImpl(); 193 Long millis = date.atStartOfDay(ZoneId.systemDefault()).toInstant().toEpochMilli(); 194 dateAttrs.addCDATAAttribute("millis", Long.toString(millis)); 195 XMLUtils.createElement(contentHandler, "date", dateAttrs, dateType.toString(date)); 196 197 Date pubDate = content.getLastMajorValidationDate(); 198 dateAttrs.clear(); 199 dateAttrs.addCDATAAttribute("millis", Long.toString(pubDate.getTime())); 200 XMLUtils.createElement(contentHandler, "pubDate", dateAttrs, DateUtils.dateToString(pubDate)); 201 202 if (saxContent) 203 { 204 _saxNewsletterContent(content); 205 } 206 207 XMLUtils.endElement(contentHandler, "newsletter"); 208 } 209 210 /** 211 * Sax the newsletter content. 212 * @param content the newsletter content. 213 * @throws SAXException if an error occurred while saxing 214 * @throws IOException if an I/O error occurred 215 */ 216 protected void _saxNewsletterContent(Content content) throws SAXException, IOException 217 { 218 XMLUtils.startElement(contentHandler, "newsletter-content"); 219 220 String uri = "cocoon://_content.xml?contentId=" + content.getId() + "&viewName=main"; 221 SitemapSource src = null; 222 223 try 224 { 225 src = (SitemapSource) _sourceResolver.resolveURI(uri); 226 src.toSAX(new IgnoreRootHandler(contentHandler)); 227 } 228 finally 229 { 230 _sourceResolver.release(src); 231 } 232 233 XMLUtils.endElement(contentHandler, "newsletter-content"); 234 } 235 236 /** 237 * Sax the link to the newsletter archives RSS feed. 238 * @param category the category. 239 * @throws SAXException if an error occurred while saxing 240 * @throws ProcessingException if an error occurred 241 */ 242 protected void _saxRssFeed(Category category) throws SAXException, ProcessingException 243 { 244 String categoryId = category.getId(); 245 String siteName = category.getSiteName(); 246 String lang = category.getLang(); 247 248 String encodedCategoryId = FilenameUtils.encodePath(categoryId); 249 String url = "plugins/newsletter/" + siteName + "/" + lang + "/archives/" + encodedCategoryId + "/rss.xml"; 250 251 AttributesImpl attrs = new AttributesImpl(); 252 attrs.addCDATAAttribute("url", URIUtils.encodePath(url)); 253 254 XMLUtils.createElement(contentHandler, "rss", attrs); 255 } 256}