001: /*
002: * ====================================================================
003: * JAFFA - Java Application Framework For All
004: *
005: * Copyright (C) 2002 JAFFA Development Group
006: *
007: * This library is free software; you can redistribute it and/or
008: * modify it under the terms of the GNU Lesser General Public
009: * License as published by the Free Software Foundation; either
010: * version 2.1 of the License, or (at your option) any later version.
011: *
012: * This library is distributed in the hope that it will be useful,
013: * but WITHOUT ANY WARRANTY; without even the implied warranty of
014: * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
015: * Lesser General Public License for more details.
016: *
017: * You should have received a copy of the GNU Lesser General Public
018: * License along with this library; if not, write to the Free Software
019: * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
020: *
021: * Redistribution and use of this software and associated documentation ("Software"),
022: * with or without modification, are permitted provided that the following conditions are met:
023: * 1. Redistributions of source code must retain copyright statements and notices.
024: * Redistributions must also contain a copy of this document.
025: * 2. Redistributions in binary form must reproduce the above copyright notice,
026: * this list of conditions and the following disclaimer in the documentation
027: * and/or other materials provided with the distribution.
028: * 3. The name "JAFFA" must not be used to endorse or promote products derived from
029: * this Software without prior written permission. For written permission,
030: * please contact mail to: jaffagroup@yahoo.com.
031: * 4. Products derived from this Software may not be called "JAFFA" nor may "JAFFA"
032: * appear in their names without prior written permission.
033: * 5. Due credit should be given to the JAFFA Project (http://jaffa.sourceforge.net).
034: *
035: * THIS SOFTWARE IS PROVIDED "AS IS" AND ANY EXPRESSED OR IMPLIED
036: * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
037: * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
038: * DISCLAIMED. IN NO EVENT SHALL THE APACHE SOFTWARE FOUNDATION OR
039: * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
040: * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
041: * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
042: * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
043: * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
044: * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
045: * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
046: * SUCH DAMAGE.
047: * ====================================================================
048: */
049:
050: package org.jaffa.presentation.portlet;
051:
052: import java.io.BufferedReader;
053: import java.io.IOException;
054: import java.io.StringReader;
055: import java.io.UnsupportedEncodingException;
056: import java.net.URLDecoder;
057: import java.util.*;
058: import javax.servlet.http.HttpServletRequest;
059: import org.apache.log4j.Logger;
060: import org.jaffa.presentation.portlet.component.Component;
061: import org.jaffa.presentation.portlet.session.UserSession;
062: import org.jaffa.util.StringHelper;
063: import org.xml.sax.Attributes;
064: import org.xml.sax.InputSource;
065: import org.xml.sax.XMLReader;
066: import org.xml.sax.helpers.DefaultHandler;
067: import org.xml.sax.helpers.XMLReaderFactory;
068:
069: /** This is a helper class for marshalling the HistoryNavList into XML and vice versa.
070: *
071: * @author GautamJ
072: */
073: public class HistoryNav {
074:
075: private static Logger log = Logger.getLogger(HistoryNav.class);
076:
077: /** Constant to denote the 'historyNav' parameter.*/
078: public static final String HISTORY_NAV_PARAMETER = "historyNav";
079:
080: private static final String XML_FORM_KEYS = "form-keys";
081: private static final String XML_FORM_KEYS_START = '<' + XML_FORM_KEYS + '>';
082: private static final String XML_FORM_KEYS_END = "</"
083: + XML_FORM_KEYS + '>';
084: private static final String XML_FORM_KEY = "form-key";
085: private static final String XML_FORM_KEY_START = '<' + XML_FORM_KEY + '>';
086: private static final String XML_FORM_KEY_END = "</" + XML_FORM_KEY
087: + '>';
088: private static final String XML_FORM_NAME = "form-name";
089: private static final String XML_FORM_NAME_START = '<' + XML_FORM_NAME + '>';
090: private static final String XML_FORM_NAME_END = "</"
091: + XML_FORM_NAME + '>';
092: private static final String XML_COMPONENT_ID = "component-id";
093: private static final String XML_COMPONENT_ID_START = '<' + XML_COMPONENT_ID + '>';
094: private static final String XML_COMPONENT_ID_END = "</"
095: + XML_COMPONENT_ID + '>';
096: private static final String XML_TITLE = "title";
097: private static final String XML_TITLE_START = '<' + XML_TITLE + '>';
098: private static final String XML_TITLE_END = "</" + XML_TITLE + '>';
099:
100: /** This will marhsal the input List into XML. The List is assumed to contain FormKey objects.
101: * @param historyNavList The List of FormKey objects.
102: * @return The XML representation of the historyNavList.
103: */
104: public static String encode(List historyNavList) {
105: StringBuffer buf = new StringBuffer();
106: if (historyNavList != null && historyNavList.size() > 0) {
107: buf.append(XML_FORM_KEYS_START);
108: for (Iterator itr = historyNavList.iterator(); itr
109: .hasNext();) {
110: FormKey fk = (FormKey) itr.next();
111: buf.append(XML_FORM_KEY_START);
112: if (fk.getFormName() != null) {
113: buf.append(XML_FORM_NAME_START);
114: buf.append(fk.getFormName());
115: buf.append(XML_FORM_NAME_END);
116: }
117: if (fk.getComponentId() != null) {
118: buf.append(XML_COMPONENT_ID_START);
119: buf.append(fk.getComponentId());
120: buf.append(XML_COMPONENT_ID_END);
121: }
122: if (fk.getTitle() != null) {
123: buf.append(XML_TITLE_START);
124: buf.append(fk.getTitle());
125: buf.append(XML_TITLE_END);
126: }
127: buf.append(XML_FORM_KEY_END);
128: }
129: buf.append(XML_FORM_KEYS_END);
130: }
131: return buf.toString();
132: }
133:
134: /** This will unmarshal the input XML into a List of FormKey objects.
135: * @param historyNavXml The XML representation of the historyNavList.
136: * @return The List of FormKey objects.
137: */
138: public static List decode(String historyNavXml) {
139: List historyNavList = null;
140: try {
141: // The following step may seem out of place.
142: // And the correct thing to do is to probably do a convertToHtml() on the String returned by the encode() method.
143: // However, on a Post, the browser implicitly converts all the entities to corresponding characters.
144: // Hence the need for the following step !!
145: historyNavXml = StringHelper.replace(historyNavXml, "&",
146: "&");
147:
148: if (log.isDebugEnabled())
149: log.debug("Unmarshalling the historyNavXml "
150: + historyNavXml);
151: XMLReader reader = XMLReaderFactory.createXMLReader();
152: HistoryNavHandler handler = new HistoryNavHandler();
153: reader.setContentHandler(handler);
154: reader.parse(new InputSource(new BufferedReader(
155: new StringReader(historyNavXml))));
156: historyNavList = handler.getHistoryNavList();
157: } catch (Exception e) {
158: if (log.isInfoEnabled())
159: log.info("Error while parsing the historyNavXml "
160: + historyNavXml, e);
161: }
162: if (log.isDebugEnabled())
163: log.debug("Unmarshalled List: " + historyNavList);
164: return historyNavList;
165: }
166:
167: /** This will initialze a List with the 'jaffa_home' link and set the 'historyNav' attribute on the input request stream with the new List.
168: * @param request The request stream
169: * @return the newly initialized List.
170: */
171: public static List initializeHistoryNav(HttpServletRequest request) {
172: try {
173: return initializeHistoryNav(request, null);
174: } catch (UnsupportedEncodingException e) {
175: // this will never happen, since the finalUrl is null !!
176: return null;
177: }
178: }
179:
180: /** This will initialze a List with the 'jaffa_home' link and set the 'historyNav' attribute on the input request stream with the new List.
181: * It will also add a link for the finalUrl, the title for which should be passed in as the value for the paramter "desktopName" or "title", within the finalUrl.
182: * @param request The request stream
183: * @param finalUrl The final Url encoded in UTF-8 format.
184: * @throws UnsupportedEncodingException if the UTF-8 format is not supported. should never happen.
185: * @return the newly initialized List.
186: */
187: public static List initializeHistoryNav(HttpServletRequest request,
188: String finalUrl) throws UnsupportedEncodingException {
189: // Initialize the List of FormKey objects to be displayed in the historyNav
190: List historyNavList = new ArrayList();
191:
192: // Add the link 'Home' to the historyNav
193: FormKey home = new FormKey("jaffa_home", null);
194: home.setTitle("[title.Jaffa.HistoryNav.Home]");
195: historyNavList.add(home);
196:
197: // Add the link 'desktop' to the historyNav, computing it from the finalUrl
198: // Ensure that its not the same as 'jaffa_home'
199: if (finalUrl != null && !finalUrl.equals(home.getFormName())) {
200: FormKey desktop = new FormKey(finalUrl, null);
201: desktop.setTitle(determineDesktopName(finalUrl));
202: // If we can't get a title, don't include a history link for the desktop!
203: if (desktop.getTitle() != null)
204: historyNavList.add(desktop);
205: else
206: log
207: .debug("Desktop Link not added to history - No Title available");
208: }
209:
210: // Add the historyNav to the request stream
211: request.setAttribute(HistoryNav.HISTORY_NAV_PARAMETER,
212: historyNavList);
213: return historyNavList;
214: }
215:
216: /** This will search the request stream for the attribute 'historyNav'. If not found, it'll search for the parameter 'historyNav'.
217: * This parameter is expected to be in XML format and will be decoded into a List
218: * @param request The request stream.
219: * @return The List containing the links for the HistoryNav.
220: */
221: public static List obtainHistoryNav(HttpServletRequest request) {
222: List historyNavList = null;
223:
224: // check to see if the List is present as an attribute in the request stream
225: historyNavList = (List) request
226: .getAttribute(HistoryNav.HISTORY_NAV_PARAMETER);
227: if (historyNavList == null) {
228: // now check to see if it was passed in as a parameter in the request stream
229: String historyNavXml = request
230: .getParameter(HistoryNav.HISTORY_NAV_PARAMETER);
231:
232: if (historyNavXml != null) {
233: // the String will be in XML format.. so unmarshal it into a List
234: historyNavList = decode(historyNavXml);
235: }
236: }
237:
238: return historyNavList;
239: }
240:
241: /** This will add the input FormKey to the historyNav.
242: * If the historyNav didn't exist, then one will be initialized.
243: * If the input FormKey already existed on the historyNav, then all subsequent FormKeys will be removed from the list.
244: * Also, all the subsequent components will be closed.
245: * @param request The request stream.
246: * @param fk The FormKey to add.
247: */
248: public static void addFormKeyToHistoryNav(
249: HttpServletRequest request, FormKey fk) {
250: List historyNavList = obtainHistoryNav(request);
251: if (historyNavList == null) {
252: historyNavList = initializeHistoryNav(request);
253: historyNavList.add(fk);
254: } else if (!historyNavList.contains(fk)) {
255: historyNavList.add(fk);
256: } else {
257: // close all the subsequent components, provided they are different from the current component
258: int indexOfFk = historyNavList.indexOf(fk);
259: for (int i = indexOfFk + 1; i < historyNavList.size(); i++) {
260: FormKey subsequentFk = (FormKey) historyNavList.get(i);
261: String subsequentComponentId = subsequentFk
262: .getComponentId();
263: if (subsequentComponentId != null
264: && !subsequentComponentId.equals(fk
265: .getComponentId())) {
266: Component subsequentComponent = UserSession
267: .getUserSession(request).getComponent(
268: subsequentComponentId);
269: if (subsequentComponent != null
270: && subsequentComponent.isActive())
271: subsequentComponent.quit();
272: }
273: }
274:
275: // remove all subsequent FormKeys
276: if (indexOfFk + 1 < historyNavList.size())
277: historyNavList.subList(indexOfFk + 1,
278: historyNavList.size()).clear();
279: }
280:
281: // Add the historyNav to the request stream
282: request.setAttribute(HistoryNav.HISTORY_NAV_PARAMETER,
283: historyNavList);
284: }
285:
286: /** Return the value of 'desktopName' or 'title' passed as a parameter within formName
287: */
288: private static String determineDesktopName(String formName)
289: throws UnsupportedEncodingException {
290: String desktopName = null;
291: formName = URLDecoder.decode(formName, "UTF-8");
292: int i = formName.indexOf('?');
293: if (i > -1) {
294: if (formName.length() > (i + 1)) {
295: String parameterString = formName.substring(i + 1);
296: StringTokenizer tokenizer = new StringTokenizer(
297: parameterString, "=&");
298: String attributeName = null;
299: while (tokenizer.hasMoreTokens()) {
300: String token = tokenizer.nextToken();
301: if (attributeName == null) {
302: attributeName = token;
303: } else {
304: if (attributeName.equals("desktopName")
305: || attributeName.equals("title")) {
306: desktopName = token;
307: break;
308: }
309: attributeName = null;
310: }
311: }
312: }
313: }
314: return desktopName;
315: }
316:
317: /** A private class used for SAX parsing the encoded HistoryNav */
318: private static class HistoryNavHandler extends DefaultHandler {
319: private List historyNavList = new ArrayList();
320: private Map currentElements = new HashMap();
321: private String currentElement = null;
322:
323: /** Receive notification of the start of an element.
324: * @param uri The uri.
325: * @param sName The local name (without prefix), or the empty string if Namespace processing is not being performed.
326: * @param qName The qualified name (with prefix), or the empty string if qualified names are not available.
327: * @param atts The specified or defaulted attributes
328: */
329: public void startElement(String uri, String sName,
330: String qName, Attributes atts) {
331: if (sName.equals(XML_FORM_NAME)) {
332: currentElement = XML_FORM_NAME;
333: } else if (sName.equals(XML_COMPONENT_ID)) {
334: currentElement = XML_COMPONENT_ID;
335: } else if (sName.equals(XML_TITLE)) {
336: currentElement = XML_TITLE;
337: }
338: }
339:
340: /** Receive notification of character data inside an element.
341: * @param ch The characters.
342: * @param start The start position in the character array.
343: * @param length The number of characters to use from the character array.
344: */
345: public void characters(char[] ch, int start, int length) {
346: String currentString = (String) currentElements
347: .get(currentElement);
348: String stringToAdd = new String(ch, start, length);
349: stringToAdd = stringToAdd.trim();
350: if (currentString == null) {
351: currentElements.put(currentElement, stringToAdd);
352: } else {
353: currentElements.put(currentElement, currentString
354: + stringToAdd);
355: }
356: }
357:
358: /** Receive notification of the end of an element.
359: * @param uri The uri.
360: * @param sName The local name (without prefix), or the empty string if Namespace processing is not being performed.
361: * @param qName The qualified name (with prefix), or the empty string if qualified names are not available.
362: */
363: public void endElement(String uri, String sName, String qName) {
364: if (sName.equals(XML_FORM_KEY)) {
365: FormKey fk = new FormKey((String) currentElements
366: .get(XML_FORM_NAME), (String) currentElements
367: .get(XML_COMPONENT_ID));
368: fk.setTitle((String) currentElements.get(XML_TITLE));
369: historyNavList.add(fk);
370: currentElements.clear();
371: }
372: }
373:
374: public List getHistoryNavList() {
375: return historyNavList;
376: }
377: }
378: }
|