View Javadoc
1   /*
2    * Copyright (C) 2003-2009 eXo Platform SAS.
3    *
4    * This program is free software; you can redistribute it and/or
5    * modify it under the terms of the GNU Affero General Public License
6    * as published by the Free Software Foundation; either version 3
7    * of the License, or (at your option) any later version.
8    *
9    * This program 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 General Public License for more details.
13   *
14   * You should have received a copy of the GNU General Public License
15   * along with this program; if not, see<http://www.gnu.org/licenses/>.
16   */
17  package org.exoplatform.wcm.connector.collaboration;
18  
19  import java.io.ByteArrayInputStream;
20  import java.text.DateFormat;
21  import java.text.SimpleDateFormat;
22  import java.util.ArrayList;
23  import java.util.Date;
24  import java.util.HashMap;
25  import java.util.Iterator;
26  import java.util.List;
27  import java.util.Map;
28  
29  import javax.jcr.Node;
30  import javax.ws.rs.GET;
31  import javax.ws.rs.Path;
32  import javax.ws.rs.QueryParam;
33  import javax.ws.rs.core.MediaType;
34  import javax.ws.rs.core.Response;
35  import javax.xml.parsers.DocumentBuilderFactory;
36  import javax.xml.transform.dom.DOMSource;
37  
38  import org.exoplatform.services.jcr.util.Text;
39  import org.exoplatform.services.log.ExoLogger;
40  import org.exoplatform.services.log.Log;
41  import org.exoplatform.services.rest.resource.ResourceContainer;
42  import org.exoplatform.services.wcm.core.WCMConfigurationService;
43  import org.exoplatform.services.wcm.publication.WCMComposer;
44  import org.exoplatform.services.wcm.utils.WCMCoreUtils;
45  import org.exoplatform.wcm.connector.BaseConnector;
46  import org.w3c.dom.Document;
47  
48  import com.sun.syndication.feed.synd.SyndContent;
49  import com.sun.syndication.feed.synd.SyndContentImpl;
50  import com.sun.syndication.feed.synd.SyndEntry;
51  import com.sun.syndication.feed.synd.SyndEntryImpl;
52  import com.sun.syndication.feed.synd.SyndFeed;
53  import com.sun.syndication.feed.synd.SyndFeedImpl;
54  import com.sun.syndication.io.SyndFeedOutput;
55  
56  /**
57   * Generates an RSS feed.
58   *
59   * @LevelAPI Provisional
60   * @anchor RssConnector
61   */
62  
63  @Path("/feed/")
64  public class RssConnector extends BaseConnector implements ResourceContainer {
65  
66    /** The WORKSPACE. */
67    static private String WORKSPACE = "workspace" ;
68  
69    /** The REPOSITORY. */
70    static private String REPOSITORY = "repository" ;
71  
72    /** The RS s_ version. */
73    static private String RSS_VERSION = "rss_2.0" ;
74  
75    /** The FEE d_ title. */
76    static private String FEED_TITLE = "exo:feedTitle" ;
77  
78    /** The DESCRIPTION. */
79    static private String DESCRIPTION = "exo:description" ;
80  
81    /** The TITLE. */
82    static private String TITLE = "exo:title";
83  
84    /** The LINK. */
85    static private String LINK = "exo:link" ;
86  
87    /** The SUMMARY. */
88    static private String SUMMARY = "exo:summary";
89  
90    /** The detail page */
91    static private String DETAIL_PAGE = "detail-page";
92  
93    /** The detail get param */
94    static private String DETAIL_PARAM = "detail-param";
95  
96    /** The wcm composer service. */
97    private WCMComposer wcmComposer;
98  
99    /** The wcm configuration service. */
100   private WCMConfigurationService wcmConfigurationService;
101   /** The log. */
102   private static final Log LOG = ExoLogger.getLogger(RssConnector.class.getName());
103 
104   /**
105    * Instantiates a new RSS connector.
106    */
107   public RssConnector() {
108     this.wcmConfigurationService = WCMCoreUtils.getService(WCMConfigurationService.class);    
109     this.wcmComposer = WCMCoreUtils.getService(WCMComposer.class);
110   }
111 
112   /**
113    * Generates an RSS feed.
114    *
115    * @param repository The repository name.
116    * @param workspace The workspace name.
117    * @param server The server.
118    * @param siteName The name of site.
119    * @param title The title of the feed.
120    * @param desc  The description of the feed.
121    * @param folderPath The folder path of the feed.
122    * @param orderBy The criteria to order the content.
123    * @param orderType The descending or ascending order.
124    * @param lang   The language of the feed.
125    * @param detailPage The page used to open the content.
126    * @param detailParam The parameters are the key in the URL to let CLV know which really is the path in the current URL.
127    * @param recursive This param is deprecated and will be moved soon.
128    * @return The response.
129    * @throws Exception The exception
130    * 
131    * @anchor RssConnector.generate
132    */
133   @GET
134   @Path("/rss/")
135   public Response generate(@QueryParam("repository") String repository,
136                            @QueryParam("workspace") String workspace,
137                            @QueryParam("server") String server,
138                            @QueryParam("siteName") String siteName,
139                            @QueryParam("title") String title,
140                            @QueryParam("desc") String desc,
141                            @QueryParam("folderPath") String folderPath,
142                            @QueryParam("orderBy") String orderBy,
143                            @QueryParam("orderType") String orderType,
144                            @QueryParam("lang") String lang,
145                            @QueryParam("detailPage") String detailPage,
146                            @QueryParam("detailParam") String detailParam,
147                            @QueryParam("recursive") String recursive) throws Exception {
148     Map<String, String> contextRss = new HashMap<String, String>();
149     contextRss.put(REPOSITORY, repository);
150     contextRss.put(WORKSPACE, workspace);
151     contextRss.put(RSS_VERSION, "rss_2.0");
152     contextRss.put(FEED_TITLE, title);
153     if (desc == null)
154       desc = "Powered by eXo WCM-" + WCMCoreUtils.getProjectVersion();
155     contextRss.put(DESCRIPTION, desc);
156 
157     contextRss.put(LINK, server);
158 
159     if (detailPage == null)
160       detailPage = wcmConfigurationService.getRuntimeContextParam(WCMConfigurationService.PARAMETERIZED_PAGE_URI);
161     contextRss.put(DETAIL_PAGE, detailPage);
162     if (detailParam == null) detailParam  = "content-id";
163     contextRss.put(DETAIL_PARAM, detailParam);
164 
165 
166     HashMap<String, String> filters = new HashMap<String, String>();
167     filters.put(WCMComposer.FILTER_MODE, WCMComposer.MODE_LIVE);
168     if (orderType == null) orderType = "DESC";
169     if (orderBy == null) orderBy = "publication:liveDate";
170     if (lang == null) lang = "en";
171     filters.put(WCMComposer.FILTER_ORDER_BY, orderBy);
172     filters.put(WCMComposer.FILTER_ORDER_TYPE, orderType);
173     filters.put(WCMComposer.FILTER_LANGUAGE, lang);
174     filters.put(WCMComposer.FILTER_RECURSIVE, recursive);
175 
176     String path=null;
177     if (folderPath!=null) {
178       path = folderPath;
179     }
180 
181     List<Node> nodes = wcmComposer.getContents(workspace,
182                                                path,
183                                                filters,
184                                                WCMCoreUtils.getUserSessionProvider());
185 
186     String feedXML = generateRSS(nodes, contextRss);
187 
188     Document document = DocumentBuilderFactory.newInstance()
189                                               .newDocumentBuilder()
190                                               .parse(new ByteArrayInputStream(feedXML.getBytes()));
191 
192     DateFormat dateFormat = new SimpleDateFormat(IF_MODIFIED_SINCE_DATE_FORMAT);
193     Response response = Response.ok(new DOMSource(document), MediaType.TEXT_XML)
194                                 .header(LAST_MODIFIED_PROPERTY, dateFormat.format(new Date()))
195                                 .build();
196     return response;
197   }
198 
199   /**
200    * Generates RSS.
201    *
202    * @param context The context.
203    * @return the string
204    */
205   private String generateRSS(List<Node> nodes , Map<String, String> context) {
206     String rssVersion = context.get(RSS_VERSION) ;
207     String feedTitle = context.get(FEED_TITLE) ;
208     String feedDescription = context.get(DESCRIPTION) ;
209     String feedLink = context.get(LINK) ;
210     String detailPage = context.get(DETAIL_PAGE) ;
211     String detailParam = context.get(DETAIL_PARAM) ;
212     String repository = context.get(REPOSITORY) ;
213     String workspace = context.get(WORKSPACE) ;
214     String contentUrl;
215     if (!feedLink.endsWith("/")) {
216       contentUrl = feedLink + "/" + detailPage + "?" + detailParam + "=/" + repository + "/"
217           + workspace;
218     } else {
219       contentUrl = feedLink + detailPage + "?" + detailParam + "=/" + repository + "/" + workspace;
220     }
221 
222     if(feedTitle == null || feedTitle.length() == 0) feedTitle = "" ;
223     try {
224 
225       SyndFeed feed = new SyndFeedImpl();
226       feed.setFeedType(rssVersion);
227       feed.setTitle(feedTitle.replaceAll("&nbsp;", " "));
228       feed.setLink(feedLink);
229       feed.setDescription(feedDescription.replaceAll("&nbsp;", " "));
230       feed.setEncoding("UTF-8");
231 
232       List<SyndEntry> entries = new ArrayList<SyndEntry>();
233       Iterator<Node> iter = nodes.iterator();
234       while (iter.hasNext()) {
235         Node node = iter.next();
236         
237         StringBuilder url= new StringBuilder();
238         url.append(contentUrl);
239         if (node.isNodeType("nt:frozenNode")) {
240           String uuid = node.getProperty("jcr:frozenUuid").getString();
241           Node originalNode = node.getSession().getNodeByUUID(uuid);
242           url.append(originalNode.getPath());
243           url.append("&version=");
244           url.append(node.getParent().getName());
245         } else {
246           url.append(node.getPath());
247         }
248         
249         SyndEntry entry = new SyndEntryImpl();
250 
251         if (node.hasProperty(TITLE)) {
252           String nTitle = node.getProperty(TITLE).getString();
253           //encoding
254           nTitle = Text.unescapeIllegalJcrChars(new String(nTitle.getBytes("UTF-8")));
255           entry.setTitle(Text.encodeIllegalXMLCharacters(nTitle).replaceAll("&quot;", "\"").replaceAll("&apos;", "\'"));
256         }
257         else entry.setTitle("") ;
258 
259         entry.setLink(url.toString());
260         SyndContent description = new SyndContentImpl();
261         description.setType("text/plain");
262 
263         if (node.hasProperty(SUMMARY)){
264           String summary=Text.encodeIllegalXMLCharacters(node.getProperty(SUMMARY)
265                                                          .getString())
266                          .replaceAll("&nbsp;", " ").replaceAll("&amp;quot;", "\"").replaceAll("&apos;", "\'");
267           description.setValue(summary);
268         }else
269           description.setValue("");
270 
271         entry.setDescription(description);
272         entries.add(entry);
273         entry.getEnclosures() ;
274       }
275       feed.setEntries(entries);
276 
277       SyndFeedOutput output = new SyndFeedOutput();
278       return output.outputString(feed);
279     } catch (Exception e) {
280       if (LOG.isErrorEnabled()) {
281         LOG.error("Error when perform generateRSS: ", e);
282       }
283     }
284     return null;
285   }
286 
287   /* (non-Javadoc)
288    * @see org.exoplatform.wcm.connector.BaseConnector#getContentStorageType()
289    */
290   protected String getContentStorageType() throws Exception {
291     return null;
292   }
293 
294   /* (non-Javadoc)
295    * @see org.exoplatform.wcm.connector.BaseConnector#getRootContentStorage(javax.jcr.Node)
296    */
297   protected Node getRootContentStorage(Node node) throws Exception {
298     return null;
299   }
300 }