001: /* Copyright 2002 The JA-SIG Collaborative. All rights reserved.
002: * See license distributed with this file and
003: * available online at http://www.uportal.org/license.html
004: */
005:
006: package org.jasig.portal.layout;
007:
008: import java.util.Collections;
009: import java.util.Enumeration;
010: import java.util.HashMap;
011: import java.util.Map;
012:
013: import javax.servlet.http.HttpServletRequest;
014: import javax.xml.transform.Transformer;
015: import javax.xml.transform.TransformerFactory;
016: import javax.xml.transform.dom.DOMSource;
017: import javax.xml.transform.sax.SAXResult;
018:
019: import org.jasig.portal.ChannelDefinition;
020: import org.jasig.portal.ChannelParameter;
021: import org.jasig.portal.ChannelRegistryStoreFactory;
022: import org.jasig.portal.PortalException;
023: import org.jasig.portal.UserPreferences;
024: import org.apache.commons.logging.Log;
025: import org.apache.commons.logging.LogFactory;
026: import org.jasig.portal.layout.node.IUserLayoutChannelDescription;
027: import org.jasig.portal.layout.node.IUserLayoutNodeDescription;
028: import org.jasig.portal.layout.node.UserLayoutChannelDescription;
029: import org.jasig.portal.security.IPerson;
030: import org.jasig.portal.utils.CommonUtils;
031: import org.jasig.portal.utils.DocumentFactory;
032: import org.jasig.portal.utils.SAX2FilterImpl;
033: import org.w3c.dom.Document;
034: import org.w3c.dom.Element;
035: import org.xml.sax.Attributes;
036: import org.xml.sax.ContentHandler;
037: import org.xml.sax.SAXException;
038: import org.xml.sax.XMLReader;
039: import org.xml.sax.helpers.AttributesImpl;
040:
041: /**
042: * Wraps {@link IUserLayoutManager} interface to provide ability to
043: * incorporate channels into a user layout that are not part of
044: * their layout structure (persistent).
045: *
046: * The channels are incorporated upon request (functional name)
047: * and remain part of the layout structure only as long as they
048: * are the target channel.
049: *
050: * @author <a href="mailto:kstacks@sct.com">Keith Stacks</a>
051: * @version $Revision: 36164 $
052: */
053: public class TransientUserLayoutManagerWrapper implements
054: IUserLayoutManager {
055:
056: private static final Log log = LogFactory
057: .getLog(TransientUserLayoutManagerWrapper.class);
058:
059: // transient folder's subscribe id <'f'older><'t'ransient><id>
060: public final static String TRANSIENT_FOLDER_ID = "ft1";
061: // channel subscription prefix <'c'hannel><'t'ransient><'f'older>
062: public final static String SUBSCRIBE_PREFIX = "ctf";
063:
064: // The original user layout manager
065: private IUserLayoutManager man = null;
066:
067: // contains fname --> subscribe id mappings (for transient channels only)
068: private Map mFnameMap = Collections.synchronizedMap(new HashMap());
069: private Map mSubIdMap = Collections.synchronizedMap(new HashMap());
070: // stores channel defs by subscribe id (transient channels only)
071: private Map mChanMap = Collections.synchronizedMap(new HashMap());
072:
073: // current root/focused subscribe id
074: private String mFocusedId = "";
075: // subscription id counter for generating subscribe ids
076: private int mSubId = 0;
077:
078: public TransientUserLayoutManagerWrapper(IUserLayoutManager manager)
079: throws PortalException {
080: this .man = manager;
081: if (man == null) {
082: throw new PortalException(
083: "Cannot wrap a null IUserLayoutManager !");
084: }
085: }
086:
087: public IUserLayoutManager getOriginalLayoutManager()
088: throws PortalException {
089: return man;
090: }
091:
092: public void setOriginalLayoutManager(IUserLayoutManager man)
093: throws PortalException {
094: this .man = man;
095: }
096:
097: public IUserLayout getUserLayout() throws PortalException {
098: return man.getUserLayout();
099: }
100:
101: public void setUserLayout(IUserLayout userLayout)
102: throws PortalException {
103: man.setUserLayout(userLayout);
104: }
105:
106: public void getUserLayout(ContentHandler ch) throws PortalException {
107: man.getUserLayout(new TransientUserLayoutManagerSAXFilter(ch));
108: }
109:
110: public void getUserLayout(String nodeId, ContentHandler ch)
111: throws PortalException {
112: IUserLayoutNodeDescription node = this .getNode(nodeId);
113: if (null != node) {
114: IUserLayoutNodeDescription layoutNode = null;
115: try {
116: layoutNode = man.getNode(nodeId);
117: } catch (PortalException pe) {
118: // disregard. This means that the node isn't had within the
119: // nested layout manager so we can use the transient one.
120: }
121: if (layoutNode != null)
122: man.getUserLayout(nodeId,
123: new TransientUserLayoutManagerSAXFilter(ch));
124: else {
125: Document doc = DocumentFactory.getNewDocument();
126: try {
127: Element e = node.getXML(doc);
128: doc.appendChild(e);
129: Transformer trans = TransformerFactory
130: .newInstance().newTransformer();
131: trans
132: .transform(
133: new DOMSource(doc),
134: new SAXResult(
135: new TransientUserLayoutManagerSAXFilter(
136: ch)));
137: } catch (Exception e) {
138: throw new PortalException(
139: "Encountered an exception trying to output user layout",
140: e);
141: }
142: }
143: }
144: }
145:
146: public void setLayoutStore(IUserLayoutStore ls) {
147: man.setLayoutStore(ls);
148: }
149:
150: public Document getUserLayoutDOM() throws PortalException {
151: return man.getUserLayoutDOM();
152: }
153:
154: public void loadUserLayout() throws PortalException {
155: man.loadUserLayout();
156: }
157:
158: public void saveUserLayout() throws PortalException {
159: man.saveUserLayout();
160: }
161:
162: public IUserLayoutNodeDescription getNode(String nodeId)
163: throws PortalException {
164: // check to see if it's in the layout first, if not then
165: // build it..
166: IUserLayoutNodeDescription ulnd = null;
167:
168: // assume that not finding it in the implementation
169: // means that it may be a requested (transient) node.
170: try {
171: ulnd = man.getNode(nodeId);
172: } catch (PortalException pe) {
173: if (log.isDebugEnabled())
174: log.debug("Node '" + nodeId + "' is not in layout, "
175: + "checking for a transient node...");
176: }
177:
178: if (null == ulnd) {
179: // if the requested node hasn't been returned yet, it's
180: // likely it's a transient node that isn't actually part of
181: // the layout
182: ulnd = getTransientNode(nodeId);
183: }
184:
185: return ulnd;
186: }
187:
188: public IUserLayoutNodeDescription addNode(
189: IUserLayoutNodeDescription node, String parentId,
190: String nextSiblingId) throws PortalException {
191: return man.addNode(node, parentId, nextSiblingId);
192: }
193:
194: public boolean moveNode(String nodeId, String parentId,
195: String nextSiblingId) throws PortalException {
196: // allow all moves, except for those related to the transient channels and folders
197: if (nodeId != null && (!mSubIdMap.containsKey(nodeId))
198: && (!nodeId.equals(TRANSIENT_FOLDER_ID))) {
199: return man.moveNode(nodeId, parentId, nextSiblingId);
200: } else {
201: return false;
202: }
203: }
204:
205: public boolean deleteNode(String nodeId) throws PortalException {
206: // allow all deletions, except for those related to the transient channels and folders
207: if (nodeId != null && (!mSubIdMap.containsKey(nodeId))
208: && (!nodeId.equals(TRANSIENT_FOLDER_ID))) {
209: return man.deleteNode(nodeId);
210: } else {
211: return false;
212: }
213: }
214:
215: public boolean updateNode(IUserLayoutNodeDescription node)
216: throws PortalException {
217: // allow all updates, except for those related to the transient channels and folders
218: String nodeId = node.getId();
219: if (nodeId != null && (!mSubIdMap.containsKey(nodeId))
220: && (!nodeId.equals(TRANSIENT_FOLDER_ID))) {
221: return man.updateNode(node);
222: } else {
223: return false;
224: }
225: }
226:
227: public boolean canAddNode(IUserLayoutNodeDescription node,
228: String parentId, String nextSiblingId)
229: throws PortalException {
230: return man.canAddNode(node, parentId, nextSiblingId);
231: }
232:
233: public boolean canMoveNode(String nodeId, String parentId,
234: String nextSiblingId) throws PortalException {
235: // allow all moves, except for those related to the transient channels and folders
236: if (nodeId != null && (!mSubIdMap.containsKey(nodeId))
237: && (!nodeId.equals(TRANSIENT_FOLDER_ID))) {
238: return man.canMoveNode(nodeId, parentId, nextSiblingId);
239: } else {
240: return false;
241: }
242: }
243:
244: public boolean canDeleteNode(String nodeId) throws PortalException {
245: // allow all deletions, except for those related to the transient channels and folders
246: if (nodeId != null && (!mSubIdMap.containsKey(nodeId))
247: && (!nodeId.equals(TRANSIENT_FOLDER_ID))) {
248: return man.canDeleteNode(nodeId);
249: } else {
250: return false;
251: }
252: }
253:
254: public boolean canUpdateNode(IUserLayoutNodeDescription node)
255: throws PortalException {
256: // allow all updates, except for those related to the transient channels and folders
257: String nodeId = node.getId();
258: if (nodeId != null && (!mSubIdMap.containsKey(nodeId))
259: && (!nodeId.equals(TRANSIENT_FOLDER_ID))) {
260: return man.canUpdateNode(node);
261: } else {
262: return false;
263: }
264: }
265:
266: public void markAddTargets(IUserLayoutNodeDescription node)
267: throws PortalException {
268: man.markAddTargets(node);
269: }
270:
271: public void markMoveTargets(String nodeId) throws PortalException {
272: man.markMoveTargets(nodeId);
273: }
274:
275: public String getParentId(String nodeId) throws PortalException {
276: return man.getParentId(nodeId);
277: }
278:
279: public Enumeration getChildIds(String nodeId)
280: throws PortalException {
281: return man.getChildIds(nodeId);
282: }
283:
284: public String getNextSiblingId(String nodeId)
285: throws PortalException {
286: return man.getNextSiblingId(nodeId);
287: }
288:
289: public String getPreviousSiblingId(String nodeId)
290: throws PortalException {
291: return man.getPreviousSiblingId(nodeId);
292: }
293:
294: public String getCacheKey() throws PortalException {
295: // we don't need to worry about extending the base cache key here,
296: // because the transient channels are always rendered in a focused
297: // mode, that means that the user preference attributes will be
298: // sufficient to describe the layout state.
299: // In general, however one would need to append that focused channel
300: // subscribe id and fname (both are required for global scope use).
301: return man.getCacheKey();
302: }
303:
304: public int getLayoutId() {
305: return man.getLayoutId();
306: }
307:
308: public String getRootFolderId() {
309: return man.getRootFolderId();
310: }
311:
312: /**
313: * Returns the depth of a node in the layout tree.
314: *
315: * @param nodeId a <code>String</code> value
316: * @return a depth value
317: * @exception PortalException if an error occurs
318: */
319: public int getDepth(String nodeId) throws PortalException {
320: return man.getDepth(nodeId);
321: }
322:
323: public IUserLayoutNodeDescription createNodeDescription(int nodeType)
324: throws PortalException {
325: return man.createNodeDescription(nodeType);
326: }
327:
328: public boolean addLayoutEventListener(LayoutEventListener l) {
329: return man.addLayoutEventListener(l);
330: }
331:
332: public boolean removeLayoutEventListener(LayoutEventListener l) {
333: return man.removeLayoutEventListener(l);
334: }
335:
336: /**
337: * Given a subscribe Id, return a ChannelDefinition.
338: *
339: * @param subId the subscribe id for the ChannelDefinition.
340: * @return a <code>ChannelDefinition</code>
341: **/
342: protected ChannelDefinition getChannelDefinition(String subId)
343: throws PortalException {
344: ChannelDefinition chanDef = (ChannelDefinition) mChanMap
345: .get(subId);
346:
347: if (null == chanDef) {
348: String fname = getFname(subId);
349: if (log.isDebugEnabled())
350: log
351: .debug("TransientUserLayoutManagerWrapper>>getChannelDefinition, "
352: + "attempting to get a channel definition using functional name: "
353: + fname);
354: try {
355: chanDef = ChannelRegistryStoreFactory
356: .getChannelRegistryStoreImpl()
357: .getChannelDefinition(fname);
358: } catch (Exception e) {
359: throw new PortalException(
360: "Failed to get channel information "
361: + "for subscribeId: " + subId);
362: }
363: mChanMap.put(subId, chanDef);
364: }
365:
366: return chanDef;
367: }
368:
369: /**
370: * Given a subscribe Id, return its functional name.
371: *
372: * @param subId the subscribe id to lookup
373: * @return the subscribe id's functional name
374: **/
375: public String getFname(String subId) {
376: return (String) mSubIdMap.get(subId);
377: }
378:
379: /**
380: * Given an functional name, return its subscribe id.
381: *
382: * @param fname the functional name to lookup
383: * @return the fname's subscribe id.
384: **/
385: public String getSubscribeId(String fname) throws PortalException {
386:
387: // see if a given subscribe id is already in the map
388: String subId = (String) mFnameMap.get(fname);
389: if (subId == null) {
390: // see if a given subscribe id is already in the layout
391: subId = man.getSubscribeId(fname);
392: }
393:
394: // obtain a description of the transient channel and
395: // assign a new transient channel id
396: if (subId == null) {
397: try {
398: ChannelDefinition chanDef = ChannelRegistryStoreFactory
399: .getChannelRegistryStoreImpl()
400: .getChannelDefinition(fname);
401: if (chanDef != null) {
402: // assign a new id
403: subId = getNextSubscribeId();
404: mFnameMap.put(fname, subId);
405: mSubIdMap.put(subId, fname);
406: mChanMap.put(subId, chanDef);
407: }
408: } catch (Exception e) {
409: log
410: .error("TransientUserLayoutManagerWrapper::getSubscribeId() : "
411: + "an exception encountered while trying to obtain "
412: + "ChannelDefinition for fname \""
413: + fname + "\" : " + e);
414: subId = null;
415: }
416: }
417: return subId;
418: }
419:
420: /**
421: * Get the current focused layout subscribe id.
422: **/
423: public String getFocusedId() {
424: return mFocusedId;
425: }
426:
427: /**
428: * Set the current focused layout subscribe id.
429: *
430: * @param subscribeId Id to be set as focused.
431: **/
432: public void setFocusedId(String subscribeId) {
433: mFocusedId = subscribeId;
434: }
435:
436: /**
437: * Return an IUserLayoutChannelDescription by way of nodeId
438: *
439: * @param nodeId the node (subscribe) id to get the channel for.
440: * @return a <code>IUserLayoutNodeDescription</code>
441: **/
442: private IUserLayoutChannelDescription getTransientNode(String nodeId)
443: throws PortalException {
444: // get fname from subscribe id
445: String fname = getFname(nodeId);
446: if (null == fname || fname.equals(""))
447: throw new PortalException(
448: "Could not find a transient node " + "for id: "
449: + nodeId);
450:
451: IUserLayoutChannelDescription ulnd = new UserLayoutChannelDescription();
452: try {
453: // check cache first
454: ChannelDefinition chanDef = (ChannelDefinition) mChanMap
455: .get(nodeId);
456:
457: if (null == chanDef) {
458: chanDef = ChannelRegistryStoreFactory
459: .getChannelRegistryStoreImpl()
460: .getChannelDefinition(fname);
461: mChanMap.put(nodeId, chanDef);
462: }
463:
464: ulnd.setId(nodeId);
465: ulnd.setName(chanDef.getName());
466: ulnd.setUnremovable(true);
467: ulnd.setImmutable(true);
468: ulnd.setHidden(false);
469: ulnd.setTitle(chanDef.getTitle());
470: ulnd.setDescription(chanDef.getDescription());
471: ulnd.setClassName(chanDef.getJavaClass());
472: ulnd.setChannelPublishId("" + chanDef.getId());
473: ulnd.setChannelTypeId("" + chanDef.getTypeId());
474: ulnd.setFunctionalName(chanDef.getFName());
475: ulnd.setTimeout(chanDef.getTimeout());
476: ulnd.setEditable(chanDef.isEditable());
477: ulnd.setHasHelp(chanDef.hasHelp());
478: ulnd.setHasAbout(chanDef.hasAbout());
479:
480: ChannelParameter[] parms = chanDef.getParameters();
481: for (int i = 0; i < parms.length; i++) {
482: ChannelParameter parm = (ChannelParameter) parms[i];
483: ulnd.setParameterValue(parm.getName(), parm.getValue());
484: ulnd.setParameterOverride(parm.getName(), parm
485: .getOverride());
486: }
487:
488: } catch (Exception e) {
489: throw new PortalException(
490: "Failed to obtain channel definition "
491: + "using fname: " + fname);
492: }
493:
494: return ulnd;
495: }
496:
497: /**
498: * Return the next sequential subscription id.
499: *
500: * @return a subscribe id
501: **/
502: private synchronized String getNextSubscribeId() {
503: mSubId++;
504: return SUBSCRIBE_PREFIX + mSubId;
505: }
506:
507: /**
508: * This filter incorporates transient channels into a layout.
509: * This provides the ability for channel definitions to reside in
510: * a layout w/out the owner having to actually have them
511: * persisted.
512: */
513: class TransientUserLayoutManagerSAXFilter extends SAX2FilterImpl {
514:
515: private static final String LAYOUT = "layout";
516: private static final String LAYOUT_FRAGMENT = "layout_fragment";
517: private static final String FOLDER = "folder";
518: private static final String CHANNEL = "channel";
519: private static final String PARAMETER = "parameter";
520:
521: public TransientUserLayoutManagerSAXFilter(
522: ContentHandler handler) {
523: super (handler);
524: }
525:
526: public TransientUserLayoutManagerSAXFilter(XMLReader parent) {
527: super (parent);
528: }
529:
530: public void startElement(String uri, String localName,
531: String qName, Attributes atts) throws SAXException {
532: // check for root node (layout_fragment for detached
533: // nodes), as that's where the transient folder/channel(s)
534: // need to be added.
535: String id = atts.getValue("ID");
536: if (null != id && id.equals(getRootFolderId())) {
537: // pass root event up the chain
538: super .startElement(uri, localName, qName, atts);
539: // create folder off of root layout to act as
540: // container for transient channels
541: AttributesImpl folderAtts = new AttributesImpl();
542: folderAtts.addAttribute("", "ID", "ID", "ID",
543: TRANSIENT_FOLDER_ID);
544: folderAtts.addAttribute("", "type", "type", "CDATA",
545: "regular");
546: folderAtts.addAttribute("", "hidden", "hidden",
547: "CDATA", "true");
548: folderAtts.addAttribute("", "unremovable",
549: "unremovable", "CDATA", "true");
550: folderAtts.addAttribute("", "immutable", "immutable",
551: "CDATA", "true");
552: folderAtts.addAttribute("", "name", "name", "CDATA",
553: "Transient Folder");
554: startElement("", FOLDER, FOLDER, folderAtts);
555: return;
556: } else if (qName.equals(FOLDER)) {
557: id = atts.getValue("ID");
558: if (null != id && id.equals(TRANSIENT_FOLDER_ID)) {
559: // pass event up the chain so it's added
560: // as a child of the root
561: super .startElement(uri, localName, qName, atts);
562:
563: // add a channel to the transient folder
564: // implementation
565: String subscribeId = "";
566: try {
567: subscribeId = getFocusedId();
568: // append channel element iff subscribeId describes
569: // a transient channel, and not a regular layout channel
570:
571: if (null != subscribeId
572: && !subscribeId.equals("")
573: && mSubIdMap.containsKey(subscribeId)) {
574: ChannelDefinition chanDef = getChannelDefinition(subscribeId);
575: AttributesImpl channelAttrs = new AttributesImpl();
576: channelAttrs.addAttribute("", "ID", "ID",
577: "ID", subscribeId);
578: channelAttrs.addAttribute("", "typeID",
579: "typeID", "CDATA", ""
580: + chanDef.getTypeId());
581: channelAttrs.addAttribute("", "hidden",
582: "hidden", "CDATA", "false");
583: channelAttrs.addAttribute("", "editable",
584: "editable", "CDATA", CommonUtils
585: .boolToStr(chanDef
586: .isEditable()));
587: channelAttrs.addAttribute("",
588: "unremovable", "unremovable",
589: "CDATA", "true");
590: channelAttrs.addAttribute("", "name",
591: "name", "CDATA", chanDef.getName());
592: channelAttrs.addAttribute("",
593: "description", "description",
594: "CDATA", chanDef.getDescription());
595: channelAttrs.addAttribute("", "title",
596: "title", "CDATA", chanDef
597: .getTitle());
598: channelAttrs.addAttribute("", "class",
599: "class", "CDATA", chanDef
600: .getJavaClass());
601: channelAttrs.addAttribute("", "chanID",
602: "chanID", "CDATA", ""
603: + chanDef.getId());
604: channelAttrs.addAttribute("", "fname",
605: "fname", "CDATA", chanDef
606: .getFName());
607: channelAttrs.addAttribute("", "timeout",
608: "timeout", "CDATA", ""
609: + chanDef.getTimeout());
610: channelAttrs.addAttribute("", "hasHelp",
611: "hasHelp", "CDATA", CommonUtils
612: .boolToStr(chanDef
613: .hasHelp()));
614: channelAttrs.addAttribute("", "hasAbout",
615: "hasAbout", "CDATA", CommonUtils
616: .boolToStr(chanDef
617: .hasAbout()));
618:
619: startElement("", CHANNEL, CHANNEL,
620: channelAttrs);
621:
622: // now add channel parameters
623: ChannelParameter[] chanParms = chanDef
624: .getParameters();
625: for (int i = 0; i < chanParms.length; i++) {
626: AttributesImpl parmAttrs = new AttributesImpl();
627: ChannelParameter parm = (ChannelParameter) chanParms[i];
628: parmAttrs
629: .addAttribute("", "name",
630: "name", "CDATA", parm
631: .getName());
632: parmAttrs.addAttribute("", "value",
633: "value", "CDATA", parm
634: .getValue());
635:
636: startElement("", PARAMETER, PARAMETER,
637: parmAttrs);
638: endElement("", PARAMETER, PARAMETER);
639: }
640:
641: endElement("", CHANNEL, CHANNEL);
642: }
643: } catch (Exception e) {
644: log
645: .error(
646: "Could not obtain channel definition "
647: + "from database for subscribe id: "
648: + subscribeId, e);
649: }
650:
651: // now pass folder up the chain so it's closed
652: // out
653: super .endElement(uri, localName, qName);
654: return;
655: } else {
656: AttributesImpl attsImpl = new AttributesImpl(atts);
657: super .startElement(uri, localName, qName, attsImpl);
658: }
659: } else {
660: AttributesImpl attsImpl = new AttributesImpl(atts);
661: super .startElement(uri, localName, qName, attsImpl);
662: }
663: }
664: }
665:
666: /**
667: * Delegates to the wrapped layout manager's implementation.
668: */
669: public void processLayoutParameters(IPerson person,
670: UserPreferences userPrefs, HttpServletRequest req)
671: throws PortalException {
672: man.processLayoutParameters(person, userPrefs, req);
673: }
674: }
|