/**********************************************************************************
* $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;
}
}