001: /**
002: * Copyright 2006 Webmedia Group Ltd.
003: *
004: * Licensed under the Apache License, Version 2.0 (the "License");
005: * you may not use this file except in compliance with the License.
006: * You may obtain a copy of the License at
007: *
008: * http://www.apache.org/licenses/LICENSE-2.0
009: *
010: * Unless required by applicable law or agreed to in writing, software
011: * distributed under the License is distributed on an "AS IS" BASIS,
012: * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013: * See the License for the specific language governing permissions and
014: * limitations under the License.
015: **/package org.araneaframework.http.filter;
016:
017: import java.util.Collections;
018: import java.util.HashMap;
019: import java.util.Iterator;
020: import java.util.Map;
021: import org.apache.commons.lang.RandomStringUtils;
022: import org.apache.commons.logging.Log;
023: import org.apache.commons.logging.LogFactory;
024: import org.araneaframework.Component;
025: import org.araneaframework.Environment;
026: import org.araneaframework.InputData;
027: import org.araneaframework.Message;
028: import org.araneaframework.OutputData;
029: import org.araneaframework.Path;
030: import org.araneaframework.Service;
031: import org.araneaframework.Widget;
032: import org.araneaframework.core.Assert;
033: import org.araneaframework.core.ServiceFactory;
034: import org.araneaframework.core.StandardEnvironment;
035: import org.araneaframework.core.util.ExceptionUtil;
036: import org.araneaframework.framework.ManagedServiceContext;
037: import org.araneaframework.framework.ThreadContext;
038: import org.araneaframework.framework.TopServiceContext;
039: import org.araneaframework.framework.TransactionContext;
040: import org.araneaframework.framework.core.BaseFilterWidget;
041: import org.araneaframework.http.HttpInputData;
042: import org.araneaframework.http.HttpOutputData;
043: import org.araneaframework.http.PopupServiceInfo;
044: import org.araneaframework.http.PopupWindowContext;
045: import org.araneaframework.http.UpdateRegionProvider;
046: import org.araneaframework.http.support.PopupWindowProperties;
047: import org.araneaframework.http.util.JsonArray;
048: import org.araneaframework.http.util.JsonObject;
049:
050: /**
051: * @author Taimo Peelo (taimo@araneaframework.org)
052: */
053: public class StandardPopupFilterWidget extends BaseFilterWidget
054: implements PopupWindowContext, UpdateRegionProvider {
055: private static final Log log = LogFactory
056: .getLog(StandardPopupFilterWidget.class);
057:
058: /** @since 1.1 */
059: public static final String POPUP_REGION_KEY = "popups";
060:
061: /** Maps of popups where keys are service IDs(==popup IDs) and values
062: * <code>StandardPopupFilterWidget.PopupServiceInfo</code>. Used for rendering popups.*/
063: protected Map popups = new HashMap();
064: /** Used to keep track of popups that have been opened from thread and not yet known to be closed. */
065: protected Map allPopups = new HashMap();
066: /** Widget that opened this popup. Only applicable to threads. */
067: protected Widget opener = null;
068:
069: /** When creating new popup with unspecified service, popup is acquired from the factory. */
070: protected ServiceFactory threadServiceFactory;
071:
072: public void setThreadServiceFactory(ServiceFactory factory) {
073: this .threadServiceFactory = factory;
074: }
075:
076: /* ************************************************************************************
077: * PopupWindowContext interface methods
078: * ************************************************************************************/
079: public String open(Message startMessage,
080: PopupWindowProperties properties) throws Exception {
081: return open(startMessage, properties, null);
082: }
083:
084: public String open(Message startMessage,
085: PopupWindowProperties properties, Widget opener) {
086: String threadId = getRandomServiceId();
087: String topServiceId = (String) getTopServiceCtx()
088: .getCurrentId();
089:
090: Service service = threadServiceFactory
091: .buildService(getEnvironment());
092: startThreadPopupService(threadId, service);
093:
094: //add new, not yet opened popup to popup map
095: popups.put(threadId, new StandardPopupServiceInfo(topServiceId,
096: threadId, properties, getRequestURL()));
097: allPopups.put(threadId, popups.get(threadId));
098:
099: if (log.isDebugEnabled())
100: log.debug("Popup service with identifier '" + threadId
101: + "' was created.");
102:
103: OpenerRegistrationMessage msg = new OpenerRegistrationMessage(
104: opener);
105: try {
106: if (opener != null)
107: msg.send(null, service);
108: } finally {
109: if (opener != null && !msg.isDelivered())
110: log
111: .error("Opener registration message delivery failed.");
112: }
113:
114: //TODO when exception happens here, should we kill serving session thread
115: // and not open popup window at all?
116: if (startMessage != null)
117: startMessage.send(null, service);
118:
119: return threadId;
120: }
121:
122: public String open(Service service,
123: PopupWindowProperties properties, Widget opener) {
124: Assert.notNullParam(this , service, "service");
125:
126: String threadId = getRandomServiceId();
127: String topServiceId = (String) getTopServiceCtx()
128: .getCurrentId();
129:
130: startThreadPopupService(threadId, service);
131: popups.put(threadId, new StandardPopupServiceInfo(topServiceId,
132: threadId, properties, getRequestURL()));
133: allPopups.put(threadId, popups.get(threadId));
134:
135: if (log.isDebugEnabled())
136: log.debug("Popup service with identifier '" + threadId
137: + "' was created.");
138: return threadId;
139: }
140:
141: public String openMounted(final String url,
142: final PopupWindowProperties properties) {
143: Assert.notEmptyParam(this , url, "url");
144: final String threadId = getRandomServiceId();
145:
146: Service service = threadServiceFactory
147: .buildService(getEnvironment());
148: startThreadPopupService(threadId, service);
149:
150: //add new, not yet opened popup to popup map
151: popups.put(threadId, new PopupServiceInfo() {
152: public PopupWindowProperties getPopupProperties() {
153: return properties;
154: }
155:
156: public String toURL() {
157: //XXX: Should I use something more generic here?
158: return url + "?" + ThreadContext.THREAD_SERVICE_KEY
159: + "=" + threadId;
160: }
161: });
162: allPopups.put(threadId, popups.get(threadId));
163:
164: if (log.isDebugEnabled())
165: log.debug("Popup service with identifier '" + threadId
166: + "' was created for mounted URL '" + url + "'.");
167: return threadId;
168: }
169:
170: public void open(final String url,
171: final PopupWindowProperties properties) {
172: Assert.notEmptyParam(this , url, "url");
173: popups.put(getRandomServiceId(), new PopupServiceInfo() {
174: public PopupWindowProperties getPopupProperties() {
175: return properties;
176: }
177:
178: public String toURL() {
179: return url;
180: }
181: });
182: }
183:
184: public boolean close(String id) {
185: Assert.notEmptyParam(this , id, "id");
186: if (!allPopups.containsKey(id)) {
187: log
188: .warn("Attempt to close non-owned, unopened or already closed popup service with ID '"
189: + id + "'.");
190: return false;
191: }
192:
193: try {
194: closePopupThread(id);
195: } catch (Exception e) {
196: log
197: .warn("Attempt to close registered popup service with ID '"
198: + id
199: + "' has failed with exception : ."
200: + e);
201: return false;
202: } finally {
203: removePopup(id);
204: }
205:
206: if (log.isDebugEnabled())
207: log.debug("Popup service with identifier '" + id
208: + "' was closed");
209: return true;
210: }
211:
212: /**
213: * Only removes the popup from the view of this {@link StandardPopupFilterWidget}, retains the servicing session thread.
214: * @since 1.1
215: */
216: public void removePopup(String id) {
217: allPopups.remove(id);
218: popups.remove(id);
219: }
220:
221: /**
222: * @since 1.1
223: */
224: protected void closePopupThread(String id) {
225: getServiceCtx(ThreadContext.class).close(id);
226: }
227:
228: public Widget getOpener() {
229: return this .opener;
230: }
231:
232: public Map getPopups() {
233: return Collections.unmodifiableMap(popups);
234: }
235:
236: /* ************************************************************************************
237: * Widget methods
238: * ************************************************************************************/
239: protected Environment getChildWidgetEnvironment() {
240: return new StandardEnvironment(super
241: .getChildWidgetEnvironment(), PopupWindowContext.class,
242: this );
243: }
244:
245: protected void action(Path path, InputData input, OutputData output)
246: throws Exception {
247: super .action(path, input, output);
248: }
249:
250: protected void event(Path path, InputData input) throws Exception {
251: if (input.getGlobalData().containsKey(
252: PopupWindowContext.POPUPS_CLOSE_KEY)) {
253: ThreadContext threadCtx = (ThreadContext) getEnvironment()
254: .getEntry(ThreadContext.class);
255: Object id = threadCtx.getCurrentId();
256: threadCtx.close(id);
257: if (log.isDebugEnabled())
258: log.debug("Closed thread with id : " + id);
259: } else {
260: super .event(path, input);
261: }
262: }
263:
264: /**
265: * Popups are rendered by pushing <code>Map <String serviceId, PopupServiceInfo serviceInfo></code>
266: * into output under the key {@link org.araneaframework.http.PopupWindowContext}.POPUPS_KEY
267: */
268: protected void render(OutputData output) throws Exception {
269: super .render(output);
270: popups = new HashMap();
271: }
272:
273: /* ************************************************************************************
274: * Internal methods
275: * ************************************************************************************/
276: protected void startThreadPopupService(String id, Service service) {
277: startPopupService(id, service, ThreadContext.class);
278: }
279:
280: protected void startPopupService(String id, Service service,
281: Class serviceContext) {
282: getServiceCtx(serviceContext).addService(id, service);
283: }
284:
285: protected String getRandomServiceId() {
286: return RandomStringUtils.randomAlphanumeric(8);
287: }
288:
289: protected ManagedServiceContext getServiceCtx(Class contextClass) {
290: return (ManagedServiceContext) (getEnvironment()
291: .requireEntry(contextClass));
292: }
293:
294: protected TopServiceContext getTopServiceCtx() {
295: return ((TopServiceContext) getEnvironment().requireEntry(
296: TopServiceContext.class));
297: }
298:
299: protected String getRequestURL() {
300: HttpInputData input = (HttpInputData) getInputData();
301: return ((HttpOutputData) input.getOutputData()).encodeURL(input
302: .getContainerURL());
303: }
304:
305: /* ************************************************************************************
306: * PUBLIC INNER CLASSES
307: * ************************************************************************************/
308: /**
309: * Message that registers opener as creator of the popup thread.
310: */
311: public static class OpenerRegistrationMessage implements Message {
312: private boolean delivered = false;
313: private Widget opener;
314:
315: public OpenerRegistrationMessage(Widget opener) {
316: this .opener = opener;
317: }
318:
319: public void send(Object id, Component component) {
320: try {
321: if (component instanceof StandardPopupFilterWidget) {
322: execute(component);
323: } else {
324: component._getComponent().propagate(this );
325: }
326: } catch (Exception e) {
327: throw ExceptionUtil.uncheckException(e);
328: }
329: }
330:
331: protected void execute(Component component) throws Exception {
332: StandardPopupFilterWidget w = (StandardPopupFilterWidget) component;
333: w.opener = opener;
334: delivered = true;
335: }
336:
337: public boolean isDelivered() {
338: return delivered;
339: }
340: }
341:
342: public static class StandardPopupServiceInfo implements
343: PopupServiceInfo {
344: private boolean overrideTransaction = true;
345: private String topServiceId;
346: private String threadServiceId;
347: private String requestUrl;
348: private PopupWindowProperties popupProperties;
349:
350: public StandardPopupServiceInfo(String topServiceId,
351: String threadId, PopupWindowProperties popupProperties,
352: String requestUrl) {
353: this .topServiceId = topServiceId;
354: this .threadServiceId = threadId;
355: this .requestUrl = requestUrl;
356: this .popupProperties = popupProperties;
357: }
358:
359: public String getTopServiceId() {
360: return topServiceId;
361: }
362:
363: public String getThreadServiceId() {
364: return threadServiceId;
365: }
366:
367: /** @since 1.0.4 */
368: public void setTransactionOverride(boolean b) {
369: this .overrideTransaction = b;
370: }
371:
372: public String toURL() {
373: StringBuffer url = new StringBuffer(
374: requestUrl != null ? requestUrl : "");
375: url.append('?').append(
376: (TopServiceContext.TOP_SERVICE_KEY + "=")).append(
377: topServiceId);
378: if (threadServiceId != null) {
379: url
380: .append("&" + ThreadContext.THREAD_SERVICE_KEY
381: + "=");
382: url.append(threadServiceId);
383: }
384:
385: if (overrideTransaction)
386: url.append('&').append(
387: (TransactionContext.TRANSACTION_ID_KEY + "="))
388: .append(TransactionContext.OVERRIDE_KEY);
389: return url.toString();
390: }
391:
392: public PopupWindowProperties getPopupProperties() {
393: return popupProperties;
394: }
395: }
396:
397: /* ************************************************************************************
398: * Internal inner classes
399: * ************************************************************************************/
400:
401: /**
402: * @since 1.1
403: */
404: public Map getRegions() {
405: Map popups = getPopups();
406: if (popups == null || popups.isEmpty())
407: return null;
408:
409: // clear popup list
410: this .popups = new HashMap();
411:
412: JsonArray popupArray = new JsonArray();
413: for (Iterator i = popups.entrySet().iterator(); i.hasNext();) {
414: JsonObject popupObject = new JsonObject();
415: Map.Entry popup = (Map.Entry) i.next();
416: String serviceId = (String) popup.getKey();
417: PopupServiceInfo serviceInfo = (PopupServiceInfo) popup
418: .getValue();
419: popupObject.setStringProperty("popupId", serviceId);
420: popupObject
421: .setStringProperty("windowProperties", serviceInfo
422: .getPopupProperties() != null ? serviceInfo
423: .getPopupProperties().toString() : "");
424: popupObject.setStringProperty("url", serviceInfo.toURL());
425: popupArray.append(popupObject.toString());
426: }
427: Map regions = new HashMap();
428: regions.put(POPUP_REGION_KEY, popupArray.toString());
429: return regions;
430: }
431:
432: }
|