001: /*
002: * Copyright 2002 Sun Microsystems, Inc. All
003: * rights reserved. Use of this product is subject
004: * to license terms. Federal Acquisitions:
005: * Commercial Software -- Government Users
006: * Subject to Standard License Terms and
007: * Conditions.
008: *
009: * Sun, Sun Microsystems, the Sun logo, and Sun ONE
010: * are trademarks or registered trademarks of Sun Microsystems,
011: * Inc. in the United States and other countries.
012: */
013: package com.sun.portal.providers.containers.dynamic;
014:
015: import java.util.*;
016: import java.util.regex.*;
017: import java.net.URL;
018: import javax.servlet.http.*;
019:
020: import com.sun.portal.providers.*;
021: import com.sun.portal.providers.context.*;
022: import com.sun.portal.providers.containers.*;
023:
024: /**
025: *
026: * Dynamic Aggregation Container
027: *
028: * Allows a contained channel (the aggregator) to create a page with dynamic channel layout.
029: * Markup of the form <channel name='blah'> will be swapped for the contents of channel blah.
030: *
031: * Subclasses can override the following default behaviour:
032: *
033: * - the aggregator is the first channel in the selected list
034: * - getSelectedChannels() returns the entire selected list (including the aggregator)
035: * - only channels on the available list can be aggregated
036: *
037: */
038: public class DynamicAggregationContainerProvider extends
039: ContainerProviderAdapter {
040:
041: /*
042: * Known issues:
043: *
044: * - no way to decorate channels - there should be an api for this, or a DecoratingLeafContainer or somesuch
045: * - should ask aggregator for getSelectedChannels() but can't because the aggregator need not be a container
046: * - should probably ensure that each channel can only be called once in an entire hierarchy
047: * - this is actually a desktop problem and should be solved there
048: * - this would prevent recursion and other weird channel bahaviour
049: * - it would be nice if the aggregator could directly implement this interface, but currently
050: * there is no way to change the class of a portlet - we need "class=" in sun-portlet.xml
051: */
052:
053: //
054: // Provider methods
055: //
056: public StringBuffer getContent(HttpServletRequest request,
057: HttpServletResponse response) throws ProviderException {
058:
059: String aggregator = getAggregator();
060: if (aggregator == null)
061: return new StringBuffer(""); // should be null, but that is wrongly reserved for errors
062:
063: String aggregatorContent = getContainerProviderContext()
064: .getContent(request, response, getName(), aggregator)
065: .toString();
066:
067: Matcher m = channelPattern.matcher(aggregatorContent);
068:
069: //
070: // get the list of channels
071: //
072: List channels = new ArrayList();
073: while (m.find()) {
074: String channel = m.group(2);
075: String property = m.group(5);
076: if (property == null && canAggregate(channel)
077: && !channels.contains(channel))
078: // we're fetching content, the channel is on the available list, and is not a duplicate
079: channels.add(channel);
080: }
081:
082: //
083: // fetch the channel content
084: //
085: // this doesn't handle dups properly (eg, calls twice, returns once)
086: // by preventing dups above we fix this, but could break channels that
087: // generate static guids if they are placed more than once on the page
088: Map channelContentMap = getContainerProviderContext()
089: .getContent(request, response, getName(), channels,
090: getIntegerProperty("timeout"));
091:
092: //
093: // substitute the channel content
094: //
095: // XXX We'd like to use m.appendReplacement() but it interprets the replacement
096: // text for \ and $n sequences, and only JDK1.5 has m.quoteReplacement(),
097: // so we'll paste it back together the hard way here...
098: StringBuffer sb = new StringBuffer(7359);
099: int start = 0, end = 0;
100: m.reset();
101: while (m.find()) {
102: end = m.start();
103: sb.append(aggregatorContent.substring(start, end));
104: String channel = m.group(2);
105: String property = m.group(5);
106: if (property != null) {
107: // substitute a localized string property
108: String encoding = m.group(8);
109: String propertyValue = channel;
110: try {
111: propertyValue = getContainerProviderContext()
112: .getStringProperty(channel, property, true);
113: } catch (ProviderContextException pce) {
114: // XXX ignore - probably just means channel was missing - how to correctly detect this???
115: }
116: if (propertyValue != null) {
117: if ("js".equalsIgnoreCase(encoding))
118: propertyValue = propertyValue.replaceAll(
119: "([\"\'\\\\])", "\\\\$1").replaceAll(
120: "\n", "\\\\n");
121: sb.append(propertyValue);
122: }
123: } else {
124: // substitute channel content
125: StringBuffer cb = (StringBuffer) channelContentMap
126: .get(channel);
127: if (cb != null)
128: sb.append(cb);
129: }
130: start = m.end();
131: }
132: sb.append(aggregatorContent.substring(start));
133:
134: return sb;
135:
136: }
137:
138: // do we want to redirect all provider functions to the aggregator?
139: // as above, this class should really be a direct mix-in for the aggregator
140: public StringBuffer getEdit(HttpServletRequest request,
141: HttpServletResponse response) throws ProviderException {
142: String aggregator = getAggregator();
143: if (aggregator != null)
144: return getContainerProviderContext().getProvider(request,
145: getName(), aggregator).getEdit(request, response);
146: else
147: return null;
148: }
149:
150: public URL processEdit(HttpServletRequest request,
151: HttpServletResponse response) throws ProviderException {
152: String aggregator = getAggregator();
153: if (aggregator != null)
154: return getContainerProviderContext().getProvider(request,
155: getName(), aggregator).processEdit(request,
156: response);
157: else
158: return null;
159: }
160:
161: //
162: // ContainerProvider methods
163: //
164:
165: // we should probably have a smart implementation of getSelectedChannels() here
166:
167: public int getWindowState(String channelName)
168: throws ProviderException {
169: return ProviderWindowStates.NORMAL;
170: }
171:
172: public void setWindowState(String channelName, int windowState)
173: throws UnsupportedWindowStateException {
174: throw new UnsupportedWindowStateException(
175: "DynamicAggregationContainerProvider.setWindowState() unsupported");
176: }
177:
178: public int[] getSupportedWindowStates() throws ProviderException {
179: return supportedWindowStates;
180: }
181:
182: /**
183: * Subclasses can override this to provide a custom aggregator
184: *
185: * default implementation is to use the first channel on the selected list
186: */
187: public String getAggregator() throws ProviderException {
188: List selectedChannels = getSelectedChannels();
189: if (selectedChannels == null || selectedChannels.size() == 0)
190: return null;
191: else
192: return (String) selectedChannels.get(0);
193: }
194:
195: /**
196: * Subclasses can customise which channels can be aggregated
197: *
198: * default implementation is that only channels on the available list can be aggregated
199: */
200: public boolean canAggregate(String channel)
201: throws ProviderException {
202: return getAvailableChannels().contains(channel);
203: }
204:
205: static private int[] supportedWindowStates = new int[] { ProviderWindowStates.NORMAL };
206: static private Pattern channelPattern = Pattern
207: .compile("(?i)<\\s*channel\\s*name=([\"'])([^']*)\\1 *(property=([\"'])([^']*)\\4 *)?(encoding=([\"'])([^']*)\\7 *)?/?>");
208:
209: }
|