001: /**********************************************************************************
002: * $URL: https://source.sakaiproject.org/svn/web/tags/sakai_2-4-1/news-tool/tool/src/java/org/sakaiproject/news/tool/NewsAction.java $
003: * $Id: NewsAction.java 18588 2006-12-01 06:39:32Z joshua.ryan@asu.edu $
004: ***********************************************************************************
005: *
006: * Copyright (c) 2003, 2004, 2005, 2006 The Sakai Foundation.
007: *
008: * Licensed under the Educational Community License, Version 1.0 (the "License");
009: * you may not use this file except in compliance with the License.
010: * You may obtain a copy of the License at
011: *
012: * http://www.opensource.org/licenses/ecl1.php
013: *
014: * Unless required by applicable law or agreed to in writing, software
015: * distributed under the License is distributed on an "AS IS" BASIS,
016: * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
017: * See the License for the specific language governing permissions and
018: * limitations under the License.
019: *
020: **********************************************************************************/package org.sakaiproject.news.tool;
021:
022: import java.net.URL;
023: import java.util.List;
024: import java.util.Vector;
025:
026: import org.sakaiproject.cheftool.Context;
027: import org.sakaiproject.cheftool.JetspeedRunData;
028: import org.sakaiproject.cheftool.PortletConfig;
029: import org.sakaiproject.cheftool.RunData;
030: import org.sakaiproject.cheftool.VelocityPortlet;
031: import org.sakaiproject.cheftool.VelocityPortletPaneledAction;
032: import org.sakaiproject.cheftool.api.Menu;
033: import org.sakaiproject.cheftool.menu.MenuImpl;
034: import org.sakaiproject.event.api.SessionState;
035: import org.sakaiproject.event.cover.EventTrackingService;
036: import org.sakaiproject.exception.IdUnusedException;
037: import org.sakaiproject.exception.PermissionException;
038: import org.sakaiproject.news.api.NewsChannel;
039: import org.sakaiproject.news.api.NewsConnectionException;
040: import org.sakaiproject.news.api.NewsFormatException;
041: import org.sakaiproject.news.cover.NewsService;
042: import org.sakaiproject.site.api.Site;
043: import org.sakaiproject.site.api.SitePage;
044: import org.sakaiproject.site.api.ToolConfiguration;
045: import org.sakaiproject.site.cover.SiteService;
046: import org.sakaiproject.tool.api.Placement;
047: import org.sakaiproject.tool.api.ToolSession;
048: import org.sakaiproject.tool.cover.SessionManager;
049: import org.sakaiproject.tool.cover.ToolManager;
050: import org.sakaiproject.util.ResourceLoader;
051: import org.sakaiproject.util.StringUtil;
052:
053: /**
054: * <p>
055: * NewsAction is the Sakai RSS news tool.
056: * </p>
057: */
058: public class NewsAction extends VelocityPortletPaneledAction {
059: private static final long serialVersionUID = 1L;
060:
061: private static ResourceLoader rb = new ResourceLoader("news");
062:
063: /** portlet configuration parameter names. */
064: protected static final String PARAM_CHANNEL_URL = "channel-url";
065:
066: /** state attribute names. */
067: private static final String STATE_CHANNEL_TITLE = "channelTitle";
068:
069: protected static final String STATE_CHANNEL_URL = "channelUrl";
070:
071: private static final String STATE_PAGE_TITLE = "pageTitle";
072:
073: /** names of form fields for options panel. */
074: private static final String FORM_CHANNEL_TITLE = "title-of-channel";
075:
076: private static final String FORM_CHANNEL_URL = "address-of-channel";
077:
078: private static final String FORM_PAGE_TITLE = "title-of-page";
079:
080: /** State and init and context names for text options. */
081: private static final String GRAPHIC_VERSION_TEXT = "graphic_version";
082:
083: private static final String FULL_STORY_TEXT = "full_story";
084:
085: /** Basic feed access event. */
086: private static final String FEED_ACCESS = "news.read";
087:
088: /** Basic feed update event. */
089: private static final String FEED_UPDATE = "news.revise";
090:
091: /**
092: * Populate the state object, if needed.
093: */
094: protected void initState(SessionState state,
095: VelocityPortlet portlet, JetspeedRunData rundata) {
096: PortletConfig config = portlet.getPortletConfig();
097:
098: // detect that we have not done this, yet
099: if (state.getAttribute(STATE_CHANNEL_TITLE) == null) {
100: state.setAttribute(STATE_CHANNEL_TITLE, config.getTitle());
101:
102: String channelUrl = StringUtil.trimToNull(config
103: .getInitParameter(PARAM_CHANNEL_URL));
104: if (channelUrl == null) {
105: channelUrl = "";
106: }
107: state.setAttribute(STATE_CHANNEL_URL, channelUrl);
108:
109: }
110:
111: if (state.getAttribute(STATE_PAGE_TITLE) == null) {
112: SitePage p = SiteService.findPage(getCurrentSitePageId());
113: state.setAttribute(STATE_PAGE_TITLE, p.getTitle());
114: }
115:
116: if (state.getAttribute(GRAPHIC_VERSION_TEXT) == null) {
117: state.setAttribute(GRAPHIC_VERSION_TEXT, config
118: .getInitParameter(GRAPHIC_VERSION_TEXT));
119: }
120:
121: if (state.getAttribute(FULL_STORY_TEXT) == null) {
122: state.setAttribute(FULL_STORY_TEXT, config
123: .getInitParameter(FULL_STORY_TEXT));
124: }
125:
126: if (state.getAttribute(STATE_ACTION) == null) {
127: state.setAttribute(STATE_ACTION, "NewsAction");
128: }
129:
130: } // initState
131:
132: /**
133: * build the context for the Main (Layout) panel
134: *
135: * @return (optional) template name for this panel
136: */
137: public String buildMainPanelContext(VelocityPortlet portlet,
138: Context context, RunData rundata, SessionState state) {
139: // // if we are in edit permissions...
140: // String helperMode = (String) state.getAttribute(PermissionsAction.STATE_MODE);
141: // if (helperMode != null)
142: // {
143: // String template = PermissionsAction.buildHelperContext(portlet, context, rundata, state);
144: // if (template == null)
145: // {
146: // addAlert(state, rb.getString("theisone"));
147: // }
148: // else
149: // {
150: // return template;
151: // }
152: // }
153:
154: context.put("tlang", rb);
155:
156: String mode = (String) state.getAttribute(STATE_MODE);
157: if (MODE_OPTIONS.equals(mode)) {
158: return buildOptionsPanelContext(portlet, context, rundata,
159: state);
160: }
161:
162: context.put(GRAPHIC_VERSION_TEXT, state
163: .getAttribute(GRAPHIC_VERSION_TEXT));
164: context.put(FULL_STORY_TEXT, state
165: .getAttribute(FULL_STORY_TEXT));
166:
167: // build the menu
168: Menu bar = new MenuImpl(portlet, rundata, (String) state
169: .getAttribute(STATE_ACTION));
170:
171: // add options if allowed
172: addOptionsMenu(bar, (JetspeedRunData) rundata);
173: if (!bar.getItems().isEmpty()) {
174: context.put(Menu.CONTEXT_MENU, bar);
175: }
176:
177: context.put(Menu.CONTEXT_ACTION, state
178: .getAttribute(STATE_ACTION));
179: context.put(GRAPHIC_VERSION_TEXT, state
180: .getAttribute(GRAPHIC_VERSION_TEXT));
181: context.put(FULL_STORY_TEXT, state
182: .getAttribute(FULL_STORY_TEXT));
183:
184: String url = (String) state.getAttribute(STATE_CHANNEL_URL);
185:
186: NewsChannel channel = null;
187: List items = new Vector();
188:
189: try {
190: channel = NewsService.getChannel(url);
191: items = NewsService.getNewsitems(url);
192: } catch (NewsConnectionException e) {
193: // display message
194: addAlert(state, rb.getString("unavailable") + "\n\n["
195: + e.getLocalizedMessage() + "]");
196: } catch (NewsFormatException e) {
197: // display message
198: addAlert(state, rb.getString("unavailable") + "\n\n["
199: + e.getLocalizedMessage() + "]");
200: } catch (Exception e) {
201: // display message
202: addAlert(state, rb.getString("unavailable") + "\n\n["
203: + e.getLocalizedMessage() + "]");
204: }
205:
206: context.put("channel", channel);
207: context.put("news_items", items);
208:
209: try {
210: EventTrackingService.post(EventTrackingService.newEvent(
211: FEED_ACCESS, "/news/site/"
212: + SiteService.getSite(
213: ToolManager.getCurrentPlacement()
214: .getContext()).getId()
215: + "/placement/"
216: + SessionManager.getCurrentToolSession()
217: .getPlacementId(), false));
218:
219: } catch (IdUnusedException e) {
220: //should NEVER actually happen
221: if (Log.getLogger("chef").isDebugEnabled()) {
222: Log
223: .debug("chef",
224: "failed to log news access event due to invalid siteId");
225: }
226: }
227:
228: return (String) getContext(rundata).get("template") + "-Layout";
229:
230: } // buildMainPanelContext
231:
232: /**
233: * Setup for the options panel.
234: */
235: public String buildOptionsPanelContext(VelocityPortlet portlet,
236: Context context, RunData rundata, SessionState state) {
237: context.put("tlang", rb);
238: // provide "filter_type_form" with form field name for selecting a message filter
239: context.put("formfield_channel_title", FORM_CHANNEL_TITLE);
240:
241: // provide "filter_type" with the current default value for filtering messages
242: context.put("current_channel_title", (String) state
243: .getAttribute(STATE_CHANNEL_TITLE));
244:
245: // provide "filter_type_form" with form field name for selecting a message filter
246: context.put("formfield_channel_url", FORM_CHANNEL_URL);
247:
248: // provide "filter_type" with the current default value for filtering messages
249: context.put("current_channel_url", (String) state
250: .getAttribute(STATE_CHANNEL_URL));
251:
252: // provide "filter_type_form" with form field name for selecting a message filter
253: context.put("formfield_page_title", FORM_PAGE_TITLE);
254:
255: // provide "filter_type" with the current default value for filtering messages
256: context.put("current_page_title", (String) state
257: .getAttribute(STATE_PAGE_TITLE));
258:
259: SitePage p = SiteService.findPage(getCurrentSitePageId());
260: if (p.getTools() != null && p.getTools().size() == 1) {
261: // if this is the only tool on that page, display the input for the page's title
262: context.put("pageTitleEditable", Boolean.TRUE);
263: }
264:
265: // set the action for form processing
266: context.put(Menu.CONTEXT_ACTION, state
267: .getAttribute(STATE_ACTION));
268: context.put("form-submit", BUTTON + "doUpdate");
269: context.put("form-cancel", BUTTON + "doCancel");
270:
271: // pick the "-customize" template based on the standard template name
272: String template = (String) getContext(rundata).get("template");
273:
274: return template + "-customize";
275:
276: } // buildOptionsPanelContext
277:
278: /**
279: * Handle a user clicking the "Done" button in the Options panel
280: */
281: public void doUpdate(RunData data, Context context) {
282: // access the portlet element id to find our state
283: // %%% use CHEF api instead of Jetspeed to get state
284: String peid = ((JetspeedRunData) data).getJs_peid();
285: SessionState state = ((JetspeedRunData) data)
286: .getPortletSessionState(peid);
287:
288: String newChannelTitle = data.getParameters().getString(
289: FORM_CHANNEL_TITLE);
290: String currentChannelTitle = (String) state
291: .getAttribute(STATE_CHANNEL_TITLE);
292:
293: if (StringUtil.trimToNull(newChannelTitle) == null) {
294: //TODO: add more verbose message; requires language pack addition
295: addAlert(state, rb.getString("cus.franam"));
296: return;
297: } else if (!newChannelTitle.equals(currentChannelTitle)) {
298: state.setAttribute(STATE_CHANNEL_TITLE, newChannelTitle);
299: if (Log.getLogger("chef").isDebugEnabled())
300: Log.debug("chef", this
301: + ".doUpdate(): newChannelTitle: "
302: + newChannelTitle);
303:
304: // update the tool config
305: Placement placement = ToolManager.getCurrentPlacement();
306: placement.setTitle(newChannelTitle);
307:
308: // deliver an update to the title panel (to show the new title)
309: String titleId = titlePanelUpdateId(peid);
310: schedulePeerFrameRefresh(titleId);
311: }
312:
313: String newPageTitle = data.getParameters().getString(
314: FORM_PAGE_TITLE);
315: String currentPageTitle = (String) state
316: .getAttribute(STATE_PAGE_TITLE);
317:
318: if (StringUtil.trimToNull(newPageTitle) == null) {
319: //TODO: add more verbose message; requires language pack addition
320: addAlert(state, rb.getString("cus.pagnam"));
321: return;
322: } else if (!newPageTitle.equals(currentPageTitle)) {
323: SitePage p = SiteService.findPage(getCurrentSitePageId());
324: if (p.getTools() != null && p.getTools().size() == 1) {
325: // if this is the only tool on that page, update the page's title also
326: try {
327: // TODO: save site page title? -ggolden
328: Site sEdit = SiteService.getSite(ToolManager
329: .getCurrentPlacement().getContext());
330: SitePage pEdit = sEdit.getPage(p.getId());
331: pEdit.setTitle(newPageTitle);
332: SiteService.save(sEdit);
333: state.setAttribute(STATE_PAGE_TITLE, newPageTitle);
334: } catch (PermissionException e) {
335: if (Log.getLogger("chef").isDebugEnabled()) {
336: Log.debug("chef", " Caught Exception "
337: + e
338: + " user doesn't seem to have "
339: + "rights to update site: "
340: + ToolManager.getCurrentPlacement()
341: .getContext());
342: }
343: } catch (Exception e) {
344: //Probably will never happen unless the ToolManager returns bogus Site or null
345: if (Log.getLogger("chef").isDebugEnabled()) {
346: Log.debug("chef",
347: "NewsAction.doUpdate() caught Exception "
348: + e);
349: }
350: }
351: }
352: }
353:
354: String newChannelUrl = data.getParameters().getString(
355: FORM_CHANNEL_URL);
356: String currentChannelUrl = (String) state
357: .getAttribute(STATE_CHANNEL_URL);
358:
359: if (newChannelUrl == null && currentChannelUrl == null) {
360: // return to options panel with message %%%%%%%%%%%%
361: addAlert(state, rb.getString("plepro"));
362: return;
363:
364: }
365:
366: if (newChannelUrl != null) {
367: state.setAttribute(STATE_CHANNEL_URL, newChannelUrl);
368: try {
369: URL url = new URL(newChannelUrl);
370: NewsService.getChannel(url.toExternalForm());
371:
372: if (!newChannelUrl.equals(currentChannelUrl)) {
373: if (Log.getLogger("chef").isDebugEnabled())
374: Log.debug("chef", this
375: + ".doUpdate(): newChannelUrl: "
376: + newChannelUrl);
377: state.setAttribute(STATE_CHANNEL_URL, url
378: .toExternalForm());
379:
380: // update the tool config
381: Placement placement = ToolManager
382: .getCurrentPlacement();
383: placement.getPlacementConfig().setProperty(
384: PARAM_CHANNEL_URL, url.toExternalForm());
385:
386: }
387: } catch (NewsConnectionException e) {
388: // display message
389: addAlert(state, newChannelUrl + " "
390: + rb.getString("invalidfeed"));
391: return;
392: } catch (NewsFormatException e) {
393: // display message
394: addAlert(state, newChannelUrl + " "
395: + rb.getString("invalidfeed"));
396: return;
397: } catch (Exception e) {
398: // display message
399: addAlert(state, newChannelUrl + " "
400: + rb.getString("invalidfeed"));
401: return;
402: }
403:
404: try {
405: EventTrackingService.post(EventTrackingService
406: .newEvent(FEED_UPDATE, "/news/site/"
407: + SiteService.getSite(
408: ToolManager
409: .getCurrentPlacement()
410: .getContext()).getId()
411: + "/placement/"
412: + SessionManager
413: .getCurrentToolSession()
414: .getPlacementId(), true));
415:
416: } catch (IdUnusedException e) {
417: //should NEVER actually happen
418: if (Log.getLogger("chef").isDebugEnabled()) {
419: Log
420: .debug("chef",
421: "failed to log news update event due to invalid siteId");
422: }
423: }
424: }
425:
426: // we are done with customization... back to the main mode
427: state.removeAttribute(STATE_MODE);
428:
429: // re-enable auto-updates when leaving options
430: enableObservers(state);
431:
432: // commit the change
433: saveOptions();
434:
435: // refresh the whole page, title may have changed
436: scheduleTopRefresh();
437:
438: } // doUpdate
439:
440: /**
441: * Handle a user clicking the "Done" button in the Options panel
442: */
443: public void doCancel(RunData data, Context context) {
444: // access the portlet element id to find our state
445: // %%% use CHEF api instead of Jetspeed to get state
446: String peid = ((JetspeedRunData) data).getJs_peid();
447: SessionState state = ((JetspeedRunData) data)
448: .getPortletSessionState(peid);
449:
450: // we are done with customization... back to the main mode
451: state.removeAttribute(STATE_MODE);
452: state.removeAttribute(STATE_CHANNEL_URL);
453: state.removeAttribute(STATE_CHANNEL_TITLE);
454:
455: // re-enable auto-updates when leaving options
456: enableObservers(state);
457:
458: // cancel the options
459: cancelOptions();
460:
461: } // doCancel
462:
463: /**
464: * Get the current site page our current tool is placed on.
465: *
466: * @return The site page id on which our tool is placed.
467: */
468: protected String getCurrentSitePageId() {
469: ToolSession ts = SessionManager.getCurrentToolSession();
470: if (ts != null) {
471: ToolConfiguration tool = SiteService.findTool(ts
472: .getPlacementId());
473: if (tool != null) {
474: return tool.getPageId();
475: }
476: }
477:
478: return null;
479: }
480: }
|