/********************************************************************************** * $URL: https://source.sakaiproject.org/svn/web/trunk/news-tool/tool/src/java/org/sakaiproject/news/tool/NewsAction.java $ * $Id: NewsAction.java 121820 2013-03-27 11:21:14Z steve.swinsburg@gmail.com $ *********************************************************************************** * * Copyright (c) 2003, 2004, 2005, 2006, 2007, 2008 The Sakai Foundation * * Licensed under the Educational Community License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.opensource.org/licenses/ECL-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * **********************************************************************************/ package org.sakaiproject.news.tool; import java.net.URL; import java.text.DateFormat; import java.util.List; import java.util.Vector; import lombok.extern.apachecommons.CommonsLog; import org.apache.commons.lang.StringUtils; import org.sakaiproject.cheftool.Context; import org.sakaiproject.cheftool.JetspeedRunData; import org.sakaiproject.cheftool.PortletConfig; import org.sakaiproject.cheftool.RunData; import org.sakaiproject.cheftool.VelocityPortlet; import org.sakaiproject.cheftool.VelocityPortletPaneledAction; import org.sakaiproject.cheftool.api.Menu; import org.sakaiproject.cheftool.menu.MenuImpl; import org.sakaiproject.event.api.SessionState; import org.sakaiproject.event.cover.EventTrackingService; import org.sakaiproject.exception.IdUnusedException; import org.sakaiproject.exception.PermissionException; import org.sakaiproject.news.api.NewsChannel; import org.sakaiproject.news.api.NewsConnectionException; import org.sakaiproject.news.api.NewsFormatException; import org.sakaiproject.news.cover.NewsService; import org.sakaiproject.site.api.Site; import org.sakaiproject.site.api.SitePage; import org.sakaiproject.site.api.ToolConfiguration; import org.sakaiproject.site.cover.SiteService; import org.sakaiproject.time.cover.TimeService; import org.sakaiproject.tool.api.Placement; import org.sakaiproject.tool.api.ToolSession; import org.sakaiproject.tool.cover.SessionManager; import org.sakaiproject.tool.cover.ToolManager; import org.sakaiproject.util.ResourceLoader; /** * <p> * NewsAction is the Sakai RSS news tool. * </p> */ @CommonsLog public class NewsAction extends VelocityPortletPaneledAction { private static final long serialVersionUID = 1L; private static ResourceLoader rb = new ResourceLoader("news"); /** portlet configuration parameter names. */ protected static final String PARAM_CHANNEL_URL = "channel-url"; /** state attribute names. */ private static final String STATE_CHANNEL_TITLE = "channelTitle"; protected static final String STATE_CHANNEL_URL = "channelUrl"; private static final String STATE_PAGE_TITLE = "pageTitle"; /** names of form fields for options panel. */ private static final String FORM_CHANNEL_TITLE = "title-of-channel"; private static final String FORM_CHANNEL_URL = "address-of-channel"; private static final String FORM_PAGE_TITLE = "title-of-page"; /** State and init and context names for text options. */ private static final String GRAPHIC_VERSION_TEXT = "graphic_version"; private static final String FULL_STORY_TEXT = "full_story"; /** Basic feed access event. */ protected static final String FEED_ACCESS = "news.read"; /** Basic feed update event. */ protected static final String FEED_UPDATE = "news.revise"; protected static final String STATE_DETECT_REGISTERED_EVENT = "detectRegisteredEvent"; /** * Populate the state object, if needed. */ protected void initState(SessionState state, VelocityPortlet portlet, JetspeedRunData rundata) { // TODO: we might want to keep this from running for each request - but by letting it we get fresh info each time... -ggolden super.initState(state, portlet, rundata); PortletConfig config = portlet.getPortletConfig(); Placement placement = ToolManager.getCurrentPlacement(); // detect that we have not done this, yet if (state.getAttribute(STATE_CHANNEL_TITLE) == null) { String channelUrl = StringUtils.trimToNull(config.getInitParameter(PARAM_CHANNEL_URL)); if (channelUrl == null) { channelUrl = ""; } state.setAttribute(STATE_CHANNEL_URL, channelUrl); } // always set the titles because they might have been changed in Page Order Helper. state.setAttribute(STATE_CHANNEL_TITLE, config.getTitle()); SitePage p = SiteService.findPage(getCurrentSitePageId()); state.setAttribute(STATE_PAGE_TITLE, p.getTitle()); if (state.getAttribute(GRAPHIC_VERSION_TEXT) == null) { state.setAttribute(GRAPHIC_VERSION_TEXT, config.getInitParameter(GRAPHIC_VERSION_TEXT)); } if (state.getAttribute(FULL_STORY_TEXT) == null) { state.setAttribute(FULL_STORY_TEXT, config.getInitParameter(FULL_STORY_TEXT)); } if (state.getAttribute(STATE_ACTION) == null) { state.setAttribute(STATE_ACTION, "NewsAction"); } } // initState /** * build the context for the Main (Layout) panel * * @return (optional) template name for this panel */ public String buildMainPanelContext(VelocityPortlet portlet, Context context, RunData rundata, SessionState state) { context.put("tlang", rb); String mode = (String) state.getAttribute(STATE_MODE); if (MODE_OPTIONS.equals(mode)) { return buildOptionsPanelContext(portlet, context, rundata, state); } context.put(GRAPHIC_VERSION_TEXT, state.getAttribute(GRAPHIC_VERSION_TEXT)); context.put(FULL_STORY_TEXT, state.getAttribute(FULL_STORY_TEXT)); // build the menu Menu bar = new MenuImpl(portlet, rundata, (String) state.getAttribute(STATE_ACTION)); // add options if allowed addOptionsMenu(bar, (JetspeedRunData) rundata); if (!bar.getItems().isEmpty()) { context.put(Menu.CONTEXT_MENU, bar); } context.put(Menu.CONTEXT_ACTION, state.getAttribute(STATE_ACTION)); context.put(GRAPHIC_VERSION_TEXT, state.getAttribute(GRAPHIC_VERSION_TEXT)); context.put(FULL_STORY_TEXT, state.getAttribute(FULL_STORY_TEXT)); String url = (String) state.getAttribute(STATE_CHANNEL_URL); NewsChannel channel = null; List items = new Vector(); try { channel = NewsService.getChannel(url); items = NewsService.getNewsitems(url); } catch (NewsConnectionException e) { // display message addAlert(state, rb.getFormattedMessage("unavailable", new Object[]{e.getLocalizedMessage()})); if(log.isDebugEnabled()) { log.debug(e); } } catch (NewsFormatException e) { // display message addAlert(state, rb.getFormattedMessage("unavailable", new Object[]{e.getLocalizedMessage()})); if(log.isDebugEnabled()) { log.debug(e); } } catch (Exception e) { // display message addAlert(state, rb.getFormattedMessage("unavailable", new Object[]{e.getLocalizedMessage()})); if(log.isDebugEnabled()) { log.debug(e); } } context.put("channel", channel); context.put("news_items", items); DateFormat df = DateFormat.getDateTimeInstance(DateFormat.MEDIUM, DateFormat.SHORT, new ResourceLoader().getLocale()); df.setTimeZone(TimeService.getLocalTimeZone()); context.put("dateFormat", df); try { // tracking event if(state.getAttribute(FEED_ACCESS) == null) { if(state.getAttribute(STATE_DETECT_REGISTERED_EVENT) == null) { // is News tool EventTrackingService.post(EventTrackingService.newEvent(FEED_ACCESS, "/news/site/" + SiteService.getSite(ToolManager.getCurrentPlacement().getContext()).getId() + "/placement/" + SessionManager.getCurrentToolSession().getPlacementId(), false)); } } else { // extends News tool EventTrackingService.post(EventTrackingService.newEvent((String)state.getAttribute(FEED_ACCESS), "/news/site/" + SiteService.getSite(ToolManager.getCurrentPlacement().getContext()).getId() + "/placement/" + SessionManager.getCurrentToolSession().getPlacementId(), false)); } } catch (IdUnusedException e) { //should NEVER actually happen if (Log.getLogger("chef").isDebugEnabled()) { Log.debug("chef", "failed to log news access event due to invalid siteId"); } } return (String) getContext(rundata).get("template") + "-Layout"; } // buildMainPanelContext /** * Setup for the options panel. */ public String buildOptionsPanelContext(VelocityPortlet portlet, Context context, RunData rundata, SessionState state) { context.put("tlang", rb); // provide "filter_type_form" with form field name for selecting a message filter context.put("formfield_channel_title", FORM_CHANNEL_TITLE); // provide "filter_type" with the current default value for filtering messages context.put("current_channel_title", (String) state.getAttribute(STATE_CHANNEL_TITLE)); // provide "filter_type_form" with form field name for selecting a message filter context.put("formfield_channel_url", FORM_CHANNEL_URL); // provide "filter_type" with the current default value for filtering messages context.put("current_channel_url", (String) state.getAttribute(STATE_CHANNEL_URL)); // provide "filter_type_form" with form field name for selecting a message filter context.put("formfield_page_title", FORM_PAGE_TITLE); // provide "filter_type" with the current default value for filtering messages context.put("current_page_title", (String) state.getAttribute(STATE_PAGE_TITLE)); SitePage p = SiteService.findPage(getCurrentSitePageId()); if (p.getTools() != null && p.getTools().size() == 1) { // if this is the only tool on that page, display the input for the page's title context.put ("pageTitleEditable", Boolean.TRUE); } // set the action for form processing context.put(Menu.CONTEXT_ACTION, state.getAttribute(STATE_ACTION)); context.put("form-submit", BUTTON + "doUpdate"); context.put("form-cancel", BUTTON + "doCancel"); // pick the "-customize" template based on the standard template name String template = (String) getContext(rundata).get("template"); return template + "-customize"; } // buildOptionsPanelContext /** * Handle a user clicking the "Done" button in the Options panel */ public void doUpdate(RunData data, Context context) { // access the portlet element id to find our state // %%% use CHEF api instead of Jetspeed to get state String peid = ((JetspeedRunData) data).getJs_peid(); SessionState state = ((JetspeedRunData) data).getPortletSessionState(peid); String newChannelTitle = data.getParameters().getString(FORM_CHANNEL_TITLE); if (StringUtils.trimToNull(newChannelTitle) == null) { //TODO: add more verbose message; requires language pack addition addAlert(state, rb.getString("cus.franam")); return; } state.setAttribute(STATE_CHANNEL_TITLE, newChannelTitle); if (Log.getLogger("chef").isDebugEnabled()) Log.debug("chef", this + ".doUpdate(): newChannelTitle: " + newChannelTitle); // update the tool config Placement placement = ToolManager.getCurrentPlacement(); placement.setTitle(newChannelTitle); try { Site sEdit = SiteService.getSite(ToolManager.getCurrentPlacement().getContext()); SitePage pEdit = sEdit.getPage(getCurrentSitePageId()); String newPageTitle = data.getParameters().getString(FORM_PAGE_TITLE); pEdit.setTitleCustom(true); // if the news tool is the only tool on the page, then we can edit the page title if (pEdit.getTools() != null && pEdit.getTools().size() == 1) { if (StringUtils.trimToNull(newPageTitle) == null) { //TODO: add more verbose message; requires language pack addition addAlert(state, rb.getString("cus.pagnam")); } else { // if this is the only tool on that page, update the page's title also pEdit.setTitle(newPageTitle); state.setAttribute(STATE_PAGE_TITLE, newPageTitle); } } SiteService.save(sEdit); } catch (PermissionException e) { if(Log.getLogger("chef").isDebugEnabled()) { Log.debug("chef", " Caught Exception " + e + " user doesn't seem to have " + "rights to update site: " + ToolManager.getCurrentPlacement().getContext()); } } catch (Exception e) { //Probably will never happen unless the ToolManager returns bogus Site or null if(Log.getLogger("chef").isDebugEnabled()) { Log.debug("chef", "NewsAction.doUpdate() caught Exception " + e); } } String newChannelUrl = data.getParameters().getString(FORM_CHANNEL_URL); String currentChannelUrl = (String) state.getAttribute(STATE_CHANNEL_URL); if (newChannelUrl == null && currentChannelUrl == null) { // return to options panel with message %%%%%%%%%%%% addAlert(state, rb.getString("plepro")); return; } if (newChannelUrl != null) { state.setAttribute(STATE_CHANNEL_URL, newChannelUrl); try { URL url = new URL(newChannelUrl); NewsService.getChannel(url.toExternalForm()); if (!newChannelUrl.equals(currentChannelUrl)) { if (Log.getLogger("chef").isDebugEnabled()) Log.debug("chef", this + ".doUpdate(): newChannelUrl: " + newChannelUrl); state.setAttribute(STATE_CHANNEL_URL, url.toExternalForm()); // update the tool config placement.getPlacementConfig().setProperty(PARAM_CHANNEL_URL, url.toExternalForm()); } } catch (NewsConnectionException e) { // display message addAlert(state, rb.getFormattedMessage("invalidfeed", new Object[]{newChannelUrl})); if(log.isDebugEnabled()) { log.debug(e); } return; } catch (NewsFormatException e) { // display message addAlert(state, rb.getFormattedMessage("invalidfeed", new Object[]{newChannelUrl})); if(log.isDebugEnabled()) { log.debug(e); } return; } catch (Exception e) { // display message addAlert(state, rb.getFormattedMessage("invalidfeed", new Object[]{newChannelUrl})); if(log.isDebugEnabled()) { log.debug(e); } return; } try { // tracking event if(state.getAttribute(FEED_UPDATE) == null) { if(state.getAttribute(STATE_DETECT_REGISTERED_EVENT) == null) { // is News tool EventTrackingService.post(EventTrackingService.newEvent(FEED_UPDATE, "/news/site/" + SiteService.getSite(ToolManager.getCurrentPlacement().getContext()).getId() + "/placement/" + SessionManager.getCurrentToolSession().getPlacementId(), true)); } } else { // extends News tool EventTrackingService.post(EventTrackingService.newEvent((String)state.getAttribute(FEED_UPDATE) , "/news/site/" + SiteService.getSite(ToolManager.getCurrentPlacement().getContext()).getId() + "/placement/" + SessionManager.getCurrentToolSession().getPlacementId(), true)); } } catch (IdUnusedException e) { //should NEVER actually happen if (Log.getLogger("chef").isDebugEnabled()) { Log.debug("chef", "failed to log news update event due to invalid siteId"); } } } // we are done with customization... back to the main mode state.removeAttribute(STATE_MODE); // re-enable auto-updates when leaving options enableObservers(state); // commit the change saveOptions(); // refresh the whole page, title may have changed scheduleTopRefresh(); } // doUpdate /** * Handle a user clicking the "Done" button in the Options panel */ public void doCancel(RunData data, Context context) { // access the portlet element id to find our state // %%% use CHEF api instead of Jetspeed to get state String peid = ((JetspeedRunData) data).getJs_peid(); SessionState state = ((JetspeedRunData) data).getPortletSessionState(peid); // we are done with customization... back to the main mode state.removeAttribute(STATE_MODE); state.removeAttribute(STATE_CHANNEL_URL); state.removeAttribute(STATE_CHANNEL_TITLE); // re-enable auto-updates when leaving options enableObservers(state); // cancel the options cancelOptions(); } // doCancel /** * Get the current site page our current tool is placed on. * * @return The site page id on which our tool is placed. */ protected String getCurrentSitePageId() { ToolSession ts = SessionManager.getCurrentToolSession(); if (ts != null) { ToolConfiguration tool = SiteService.findTool(ts.getPlacementId()); if (tool != null) { return tool.getPageId(); } } return null; } }