/* * File : $Source: /alkacon/cvs/alkacon/com.alkacon.opencms.v8.usagereport/src/com/alkacon/opencms/v8/usagereport/CmsUpdatefeed.java,v $ * Date : $Date: 2009/02/05 09:56:20 $ * Version: $Revision: 1.1 $ * * This library is part of OpenCms - * the Open Source Content Management System * * Copyright (C) 2002 - 2008 Alkacon Software (http://www.alkacon.com) * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * For further information about Alkacon Software, please see the * company website: http://www.alkacon.com * * For further information about OpenCms, please see the * project website: http://www.opencms.org * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ package com.alkacon.opencms.v8.usagereport; import com.alkacon.opencms.v8.feeder.Messages; import org.opencms.file.CmsObject; import org.opencms.file.CmsProperty; import org.opencms.file.CmsPropertyDefinition; import org.opencms.file.CmsResource; import org.opencms.file.CmsResourceFilter; import org.opencms.file.collectors.CmsDateResourceComparator; import org.opencms.file.types.I_CmsResourceType; import org.opencms.i18n.CmsEncoder; import org.opencms.i18n.CmsLocaleManager; import org.opencms.main.CmsContextInfo; import org.opencms.main.CmsException; import org.opencms.main.OpenCms; import org.opencms.util.CmsStringUtil; import org.opencms.xml.content.CmsXmlContent; import org.opencms.xml.content.CmsXmlContentFactory; import java.io.IOException; import java.io.OutputStream; import java.io.OutputStreamWriter; import java.io.Writer; import java.util.ArrayList; import java.util.Arrays; import java.util.Calendar; import java.util.Collections; import java.util.Date; import java.util.GregorianCalendar; import java.util.Iterator; import java.util.List; import java.util.Locale; import com.sun.syndication.feed.synd.SyndContent; import com.sun.syndication.feed.synd.SyndContentImpl; import com.sun.syndication.feed.synd.SyndEntry; import com.sun.syndication.feed.synd.SyndEntryImpl; import com.sun.syndication.feed.synd.SyndFeed; import com.sun.syndication.feed.synd.SyndFeedImpl; import com.sun.syndication.feed.synd.SyndImage; import com.sun.syndication.feed.synd.SyndImageImpl; import com.sun.syndication.io.FeedException; import com.sun.syndication.io.SyndFeedOutput; /** * Creates a syndication feed based on a XML content that uses the AlkaconV8Updatefeed schema XSD.<p> * * The main-class is CmsUpdateFeed it creates a syndication feed based on the XML content that uses * the AlkaconV8Updatefeed schema XSD. The classes named CmsUpdateFeedConfig... are Beans which help * to save the configuration from the xml content except the CmsUpdateFeedConfigFolderComparator * which helps to sort the CmsUpdateFeedConfigFolder-objects according to their folder-path.<p> * * The class CmsUpdateFeedManager is used as action class in the module configuration and it * is need to get the admin-cms-object.<p> * * @author Ruediger Kurz * * @version $Revision: 1.1 $ */ public class CmsUpdatefeed { /** Name of the required outer node for the XSD that defines the feed content. */ public static final String NODE_SCHEMA = "AlkaconV8Updatefeeds"; /** Content type "text/plain". */ public static final String CONTENT_TYPE_TEXT = "text/plain"; /** The exclude property key. */ public static final String PROPERTY_EXCLUDE = "sitemap_hidden"; /** The String for added configuration. */ public static final String CONFIG_ADDED = "added"; /** The String for added and new configuration. */ public static final String CONFIG_NEWANDADDED = "addedand"; /** The module name. */ private static final String MODULE_NAME = "com.alkacon.opencms.v8.usagereport"; /** The module parameter for lower factor. */ private static final String MODULE_PARAM_TIMEFRAME_FACTOR_1 = "timeframe.factor1"; /** The module parameter for upper factor. */ private static final String MODULE_PARAM_TIMEFRAME_FACTOR_2 = "timeframe.factor2"; /** The current users OpenCms context. */ private CmsObject m_cms; /** The XML content that contains the definition of the feed. */ private CmsXmlContent m_content; /** The locale to use. */ private Locale m_locale; /** The resource that defines the Link for the feed. */ private CmsResource m_res; /** The schema name. */ private String m_schemaName; /** The Configuration of the xml-content. */ private CmsUpdatefeedConfig m_config; /** The resources that are candidates. */ private List m_resources; /** The admin-Cms-Object. */ private CmsObject m_adminCms; /** The clone-Cms-Object. */ private CmsObject m_myCms; /** The lower time frame factor. */ private int m_factor1; /** The upper time frame factor. */ private int m_factor2; /** * Creates a new, initialized feed based on the current URI of the given OpenCms user context.<p> * * The content must use the XSD from the Alkacon feed content definition.<p> * * @param cms the current users OpenCms context * * @throws CmsException in case something goes wrong */ public CmsUpdatefeed(CmsObject cms) throws CmsException { this(cms, cms.getRequestContext().getUri()); } /** * Creates a new feed based on the given resource.<p> * * With this constructor, the feed will not be initialized. You must call {@link #init()} first * before using the feed.<p> * * The content must use the XSD from the Alkacon feed content definition.<p> * * @param cms the current users OpenCms context * @param locale the locale to use * @param res the resource that defines the Link for the feed * * @throws CmsException in case something goes wrong */ public CmsUpdatefeed(CmsObject cms, Locale locale, CmsResource res) throws CmsException { this(cms, locale, res, CmsXmlContentFactory.unmarshal(cms, cms.readFile(res))); } /** * Creates a new feed based on the given XML content.<p> * * With this constructor, the feed will not be initialized. You must call {@link #init()} first * before using the feed.<p> * * The content must use the XSD from the Alkacon feed content definition.<p> * * @param cms the current users OpenCms context * @param locale the locale to use * @param res the resource that defines the Link for the feed * @param content the content to create the feed from */ public CmsUpdatefeed(CmsObject cms, Locale locale, CmsResource res, CmsXmlContent content) { m_cms = cms; m_locale = locale; m_res = res; m_content = content; m_schemaName = m_content.getContentDefinition().getOuterName(); // get the adminCms-object CmsUpdatefeedManager manager = (CmsUpdatefeedManager)OpenCms.getModuleManager().getModule(MODULE_NAME).getActionInstance(); m_adminCms = manager.getAdminCms(); } /** * Creates a new, initialized feed from the given resource.<p> * * The content must use the XSD from the Alkacon feed content definition.<p> * * @param cms the current users OpenCms context * @param resourceName the resource to create the feed for * * @throws CmsException in case something goes wrong */ public CmsUpdatefeed(CmsObject cms, String resourceName) throws CmsException { this(cms, OpenCms.getLocaleManager().getDefaultLocale(cms, resourceName), cms.readResource(resourceName)); init(); } /** * Initialize this object into a state so that the JSP can call * the write method to put out the RSS-feed.<p> * * Next to the different constructors and the methods which write the feed into a outputstream * there is a method called init(), which initializes the CmsUpdateFeed-object. Thereby the * module parameters are read and the admin-cms-object is transformed into a cms-object which * has the right permissions configured in the xml content. Furthermore the CmsUpdateFeedConfig... * objects are initialized. Finally the method processResources() is called to collect the * resources which should appear in the feed.<p> * * @throws CmsException in case of errors accessing the OpenCms VFS */ public void init() throws CmsException { // read the module parameter factor1 m_factor1 = 2; if (OpenCms.getModuleManager().getModule(MODULE_NAME).getParameter(MODULE_PARAM_TIMEFRAME_FACTOR_1) != null) { try { m_factor1 = Integer.parseInt(OpenCms.getModuleManager().getModule(MODULE_NAME).getParameter( MODULE_PARAM_TIMEFRAME_FACTOR_1)); } catch (NumberFormatException e) { // ignore } } // read the module parameter factor2 m_factor2 = 5; if (OpenCms.getModuleManager().getModule(MODULE_NAME).getParameter(MODULE_PARAM_TIMEFRAME_FACTOR_2) != null) { try { m_factor2 = Integer.parseInt(OpenCms.getModuleManager().getModule(MODULE_NAME).getParameter( MODULE_PARAM_TIMEFRAME_FACTOR_2)); } catch (NumberFormatException e) { // ignore } } // make sure the schema is of the correct type if (!NODE_SCHEMA.equals(m_schemaName)) { throw new CmsException(Messages.get().container(Messages.ERR_BAD_FEED_CD_2, m_schemaName, NODE_SCHEMA)); } // get the configuration for the feed m_config = new CmsUpdatefeedConfig(m_cms, m_content, m_locale); // create a CmsContextInfo-object CmsContextInfo ci = new CmsContextInfo(); ci.setUserName(m_config.getPermissions()); ci.setSiteRoot(m_cms.getRequestContext().getSiteRoot()); ci.setProjectName(m_cms.getRequestContext().currentProject().getName()); // make a CmsObject which has the configured permissions m_myCms = OpenCms.initCmsObject(m_adminCms, ci); // collect all resources which are relevant for the feed processResources(m_config.getTimeFrame() * m_factor1); if (m_resources.size() < m_config.getMinEntries()) { processResources(m_config.getTimeFrame() * m_factor2); } } /** * Collects all resources which are relevant for the feed.<p> * * This method implements the main routine to collect the resources * which fit into the configuration. It calls other local methods * to filter out the not wanted resources. What the method does:<p> * * <ul> * <li>getResources (all resources fitting in to the time frame)</li> * <li>filterLocales (all resources from the wanted locale)</li> * <li>filterExcluded (all resources which don't have set a exclude-property)</li> * <li>sort resources (sort resources according to their dateLastModified)</li> * <li>cut the list of resources (cut the list to the max entries)</li> * </ul> * * First time this method is called the parameter timeFrame is * set to the configured value multiplied by factor1. The second * time, if the resulting resources are fewer than MinEntries this * method will be called again.<p> * * * @param timeFrame an <code>int</code> which defines the time frame * * @throws CmsException in case of errors accessing the OpenCms VFS */ public void processResources(int timeFrame) throws CmsException { // get all modified resources in the given time frame List resources = getResources(timeFrame); // filter out all resources which have the wrong locale resources = filterLocales(resources); // filter out the excluded resources if (CmsStringUtil.isNotEmptyOrWhitespaceOnly(m_config.getExclude())) { resources = filterExcludes(resources); } // sort the resources by dateLastModified Collections.sort( resources, new CmsDateResourceComparator(m_myCms, Arrays.asList(new String[] {"dateLastModified"}), false)); // if the size of results is larger then maximum entries // cut the list after maximum entries if (resources.size() > m_config.getMaxEntries()) { resources = resources.subList(0, m_config.getMaxEntries()); } m_resources = resources; } /** * <b>Collects all resources which fit into the considered parameters:</b> * <ul> * <li>TimeFrame</li> * <li>ContentState</li> * <li>ResTypes</li> * </ul> * * <b>Why these Parameters have to be considered?</b><p> * The time frame is used to formalize a request which reads the all resources which last modified * date is in the given time frame for the configured folders. The ContentState and ResTypes are * Parameters which belong to each folder which should be searched through. For saving performance * the folders should only be iterated one time.<p> * * <b>What does the method do in detail?</b> * <ul> * <li>collects all resources which last modified date is in the given time frame for the configured folders</li> * <li>separates the new resources from the new and changed resources</li> * <li>removes resources which have not the correct resource-type</li> * <li>decides whether new resources are designated or new and changed resources are designated</li> * </ul> * * @param timeFrame an <code>int</code> which defines the time frame * * @return a list of CmsResource-objects * * @throws CmsException in case of errors accessing the OpenCms VFS */ public List getResources(int timeFrame) throws CmsException { Calendar cal = new GregorianCalendar(); cal.add(Calendar.HOUR_OF_DAY, timeFrame * 24 * -1); long time = cal.getTimeInMillis(); // iterate through the folders List result = new ArrayList(); Iterator folderIter = m_config.getFolders().iterator(); while (folderIter.hasNext()) { // get the resources (LastModifiedAfter(time)) for the current folder CmsUpdatefeedConfigFolder folder = (CmsUpdatefeedConfigFolder)folderIter.next(); List resourceList = m_myCms.readResources( folder.getStartfolder(), CmsResourceFilter.DEFAULT.addRequireFile().addRequireLastModifiedAfter(time), true); // create two temporary lists for separating the new or the new and changed resources List newResources = new ArrayList(); List newandchangedResources = new ArrayList(); // iterate through the resources of the current folder Iterator resIter = resourceList.iterator(); while (resIter.hasNext()) { CmsResource res = (CmsResource)resIter.next(); // separate new or new and changed resources if (res.getDateCreated() >= time) { newResources.add(res); } newandchangedResources.add(res); // remove resourcetypes which should not be in the RSS if (!folder.getResTypes().isEmpty()) { I_CmsResourceType type = OpenCms.getResourceManager().getResourceType(res.getTypeId()); if (!folder.getResTypes().contains(type.getTypeName())) { newResources.remove(res); newandchangedResources.remove(res); } } } // decides whether new resourced are designated // or new and changed resources are designated // only new resources are selected if (folder.getContentState().equals(CONFIG_ADDED)) { result.addAll(newResources); // new and changed resources are selected } else if (folder.getContentState().equals(CONFIG_NEWANDADDED)) { result.addAll(newandchangedResources); // DEFAULT: new and changed resources are selected } else { result.addAll(newandchangedResources); } } return result; } /** * Sorts all resources, which have the property exclude set on true, out.<p> * * @param resources the resources to filter * * @return a list of resources * * @throws CmsException in case of errors accessing the OpenCms VFS */ public List filterExcludes(List resources) throws CmsException { List results = new ArrayList(resources.size()); Iterator resIter = resources.iterator(); while (resIter.hasNext()) { CmsResource resource = (CmsResource)resIter.next(); CmsProperty prop = m_myCms.readPropertyObject(resource, m_config.getExclude(), true); if (CmsStringUtil.isNotEmptyOrWhitespaceOnly(prop.getValue())) { if (!prop.getValue().equals(CmsStringUtil.TRUE)) { results.add(resource); } } else { results.add(resource); } } return results; } /** * Sorts all resources which don't have the designated locale out.<p> * * @param resources the resources to filter * * @return a list of resources */ public List filterLocales(List resources) { List locales = new ArrayList(); // if languages are set in the configuration use the configured properties if (!m_config.getLanguages().isEmpty()) { Iterator langIter = m_config.getLanguages().iterator(); while (langIter.hasNext()) { String tmp = (String)langIter.next(); locales.add(new Locale(tmp)); } } // otherwise if no languages are set in the configuration take the locale // of the XML content itself as default-language else { locales.add(m_locale); } List results = new ArrayList(resources.size()); Iterator resIter = resources.iterator(); while (resIter.hasNext()) { CmsResource resource = (CmsResource)resIter.next(); Locale locale = OpenCms.getLocaleManager().getDefaultLocale(m_myCms, m_myCms.getSitePath(resource)); List available = new ArrayList(1); available.add(locale); Locale localeHit = OpenCms.getLocaleManager().getFirstMatchingLocale(locales, available); if (localeHit != null) { results.add(resource); } } return results; } /** * Creates a feed using the current settings.<p> * * @param cms the OpenCms user context to generate the feed with * @param locale the currently selected locale to use for the feed * * @return a feed created using the current settings * * @throws CmsException in case of errors accessing the OpenCms VFS * */ public SyndFeed getFeed(CmsObject cms, Locale locale) throws CmsException { // Create the feed SyndFeed feed = new SyndFeedImpl(); // Take the infos out of the config-Object and put them into the feed if (CmsStringUtil.isNotEmpty(m_config.getType())) { feed.setFeedType(m_config.getType()); } if (CmsStringUtil.isNotEmpty(m_config.getTitle())) { feed.setTitle(m_config.getTitle()); } if (CmsStringUtil.isNotEmpty(m_config.getDescription())) { feed.setDescription(m_config.getDescription()); } // process the rss feed image SyndImage image = null; if (m_config.getImage() != null) { image = new SyndImageImpl(); image.setTitle(m_config.getImage().getTitle()); image.setDescription(m_config.getImage().getDescription()); image.setUrl(m_config.getImage().getUrl()); image.setLink(m_config.getImage().getLink()); } feed.setImage(image); feed.setLanguage(locale.getLanguage()); feed.setEncoding(CmsEncoder.lookupEncoding( CmsLocaleManager.getResourceEncoding(m_cms, m_res), OpenCms.getSystemInfo().getDefaultEncoding())); feed.setLink(OpenCms.getLinkManager().getServerLink(cms, m_res.getRootPath())); // call the getEntries-Method feed.setEntries(getEntries()); return feed; } /** * * This method creates the entries of the feed.<p> * * It iterates through the resulting resources * and creates a list of SyndEntries.<p> * * @return resultEntries a SyndEntry List * * @throws CmsException in case of errors accessing the OpenCms VFS */ public List getEntries() throws CmsException { List resultEntries = new ArrayList(); Iterator resIter = m_resources.iterator(); while (resIter.hasNext()) { CmsResource r = (CmsResource)resIter.next(); // iterate through the resource properties // and save the properties "Title" and "Description" // into a String variables String title = m_cms.readPropertyObject(r, CmsPropertyDefinition.PROPERTY_TITLE, false).getValue( r.getRootPath()); String description = m_cms.readPropertyObject(r, CmsPropertyDefinition.PROPERTY_DESCRIPTION, false).getValue(); // create a SyndContent SyndContent sc = new SyndContentImpl(); sc.setValue(description); sc.setType(CONTENT_TYPE_TEXT); // create a Link String link = OpenCms.getLinkManager().getServerLink(m_cms, r.getRootPath()); /////////////////////// // build a SyndEntry // /////////////////////// SyndEntry resultEntry = new SyndEntryImpl(); resultEntry.setTitle(title); resultEntry.setLink(link); resultEntry.setPublishedDate(new Date(r.getDateLastModified())); // set the guid resultEntry.setDescription(sc); // Set the Author // Author is %(user.lastModified) if (m_config.getAuthor().equals("%(user.lastModified)")) { resultEntry.setAuthor(m_cms.readUser(r.getUserLastModified()).getFullName()); // Author is %(user.created) } else if (m_config.getAuthor().equals("%(user.created)")) { resultEntry.setAuthor(m_cms.readUser(r.getUserCreated()).getFullName()); // Author is empty } else if (CmsStringUtil.isEmptyOrWhitespaceOnly(m_config.getAuthor())) { resultEntry.setAuthor(""); // Author is any String } else { resultEntry.setAuthor(m_config.getAuthor()); } // add the SyndEntry to the resulting list resultEntries.add(resultEntry); } return resultEntries; } /** * Write the feed result to the provided output stream.<p> * * @param out the output stream to write the feed to * * @throws IOException in case of errors writing to the stream * @throws FeedException in case of errors generating the feed * @throws CmsException in case of errors accessing the OpenCms VFS */ public void write(OutputStream out) throws IOException, FeedException, CmsException { write(m_cms, m_locale, out); } /** * Write the feed result to the provided writer.<p> * * @param writer the writer to write the feed to * * @throws IOException in case of errors writing to the stream * @throws FeedException in case of errors generating the feed * @throws CmsException in case of errors accessing the OpenCms VFS */ public void write(Writer writer) throws IOException, FeedException, CmsException { write(m_cms, m_locale, writer); } /** * Write the feed result to the provided output stream.<p> * * @param cms the current users OpenCms context * @param locale the locale to use * @param out the output stream to write the feed to * * @throws IOException in case of errors writing to the stream * @throws FeedException in case of errors generating the feed * @throws CmsException in case of errors accessing the OpenCms VFS */ public void write(CmsObject cms, Locale locale, OutputStream out) throws IOException, FeedException, CmsException { Writer writer = new OutputStreamWriter(out); write(cms, locale, writer); } /** * Write the feed result to the provided writer.<p> * * @param cms the current users OpenCms context * @param locale the locale to use * @param writer the writer to write the feed to * * @throws IOException in case of errors writing to the stream * @throws FeedException in case of errors generating the feed * @throws CmsException in case of errors accessing the OpenCms VFS */ public void write(CmsObject cms, Locale locale, Writer writer) throws IOException, FeedException, CmsException { SyndFeed feed = getFeed(cms, locale); SyndFeedOutput out = new SyndFeedOutput(); out.output(feed, writer); } }