/*******************************************************************************
* Copyright 2009, 2010 Innovation Gate GmbH. All Rights Reserved.
*
* This file is part of the OpenWGA server platform.
*
* OpenWGA is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* In addition, a special exception is granted by the copyright holders
* of OpenWGA called "OpenWGA plugin exception". You should have received
* a copy of this exception along with OpenWGA in file COPYING.
* If not, see <http://www.openwga.com/gpl-plugin-exception>.
*
* OpenWGA is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with OpenWGA in file COPYING.
* If not, see <http://www.gnu.org/licenses/>.
******************************************************************************/
package de.innovationgate.wgpublisher.webtml.utils;
import java.io.ObjectStreamException;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.regex.Pattern;
import javax.servlet.http.HttpSession;
import org.apache.commons.collections.map.LinkedMap;
import de.innovationgate.utils.NullPlaceHolder;
import de.innovationgate.utils.TransientObjectWrapper;
import de.innovationgate.webgate.api.WGAPIException;
import de.innovationgate.webgate.api.WGDocument;
import de.innovationgate.webgate.api.WGIllegalArgumentException;
import de.innovationgate.webgate.api.WGIllegalStateException;
import de.innovationgate.webgate.api.WGPortlet;
import de.innovationgate.webgate.api.WGPortletRegistry;
import de.innovationgate.wga.common.CodeCompletion;
import de.innovationgate.wgpublisher.WGACore;
import de.innovationgate.wgpublisher.webtml.Base;
import de.innovationgate.wgpublisher.webtml.BaseTagStatus;
import de.innovationgate.wgpublisher.webtml.Root;
@CodeCompletion(methodMode=CodeCompletion.MODE_INCLUDE)
public class TMLPortlet implements TMLObject {
/**
* Collects all session based settings belonging to a portlet
*/
public static class SessionContext implements Serializable {
private String mode = DEFAULT_PORTLET_MODE;
private transient long lastProcessedEventIndex = Long.MIN_VALUE;
private String contextPath = null;
public long getLastProcessedEventIndex() {
return lastProcessedEventIndex;
}
public void setLastProcessedEventIndex(long lastProcessedSsEventIndex) {
this.lastProcessedEventIndex = lastProcessedSsEventIndex;
}
public String getMode() {
return mode;
}
public void setMode(String mode) {
this.mode = mode;
}
public String getContextPath() {
return contextPath;
}
public void setContextPath(String contextPath) {
if (contextPath != null && contextPath.trim().equalsIgnoreCase("none")) {
this.contextPath = null;
} else {
this.contextPath = contextPath;
}
}
private Object readResolve() throws ObjectStreamException {
lastProcessedEventIndex = Long.MIN_VALUE;
return this;
}
}
public static final String DEFAULT_PORTLET_MODE = "view";
public static final String PORTLETNAME_ITEM_PREFIX = "$pname_";
public static final Pattern PORTLETNAME_PATTERN = Pattern.compile("[a-zA-Z0-9_\\.\\-\\:]+");
public static final String PORTLET_EVENT_REQUEST_ATTR_PREFIX = "de.innovationgate.wgpublisher.webtml.utils.TMLPortlet.Events_";
private static final int EVENTQUEUE_MAX_SIZE = 1000;
public static final String PORTLETCONTEXT_NONE = "none";
private TMLUserProfile profile = null;
private WGPortlet reg = null;
public HashMap hmPortletModes = new HashMap();
public BaseTagStatus tag = null;
public List lstKeysToDelete = Collections.synchronizedList(new ArrayList());
public TMLPortlet(BaseTagStatus tag, TMLUserProfile profile, WGPortlet portlet) throws WGAPIException {
this.tag = tag;
this.profile = profile;
reg = portlet;
}
/**
* @see de.innovationgate.wgpublisher.webtml.utils.TMLObject#getAPIObject()
*/
public WGDocument getapiobject() {
return null;
}
@CodeCompletion
public Object item(String name) throws WGAPIException {
WGPortletRegistry registry = this.profile.getprofile().getPortletRegistry();
if (registry.hasItem(reg, name)) {
return TMLContext.flattenList(registry.getItemValue(reg, name));
}
else {
return profile.getprofile().getDatabase().getNoItemBehaviour().getForTMLItem();
}
}
@CodeCompletion
public List itemlist(String name) throws WGAPIException {
WGPortletRegistry registry = this.profile.getprofile().getPortletRegistry();
if (registry.hasItem(reg, name)) {
return TMLContext.toList(registry.getItemValue(reg, name));
}
else {
return profile.getprofile().getDatabase().getNoItemBehaviour().getForTMLItemList();
}
}
@CodeCompletion
public boolean hasitem(String name) throws WGAPIException {
return this.profile.getprofile().getPortletRegistry().hasItem(reg, name);
}
public Object meta(String name) throws WGAPIException {
return this.profile.meta(name);
}
public List metalist(String name) throws WGAPIException {
return this.profile.metalist(name);
}
@CodeCompletion
public boolean save() throws WGAPIException {
return this.profile.save();
}
@CodeCompletion
public boolean setitem(String name, Object value) throws WGAPIException {
this.profile.getprofile().getPortletRegistry().setItemValue(reg, name, value);
return true;
}
@CodeCompletion
public void setvar(String name, Object value) throws WGAPIException {
if (!isroot()) {
tag.tmlContext.setvar(getVarPrefix() + name, value);
}
else {
throw new WGIllegalStateException("Portlet var not allowed on root scope");
}
}
@CodeCompletion
public Object getvar(String name) throws WGAPIException {
if (!isroot()) {
return tag.tmlContext.getvar(getVarPrefix() + name);
}
else {
return null;
}
}
@CodeCompletion
public void removevar(String name) throws WGAPIException {
if (!isroot()) {
tag.tmlContext.removevar(getVarPrefix() + name);
}
}
@CodeCompletion
public void setsessionvar(String name, Object value) throws WGAPIException {
setsessionvar(name, value, true);
}
@CodeCompletion
public Object getsessionvar(String name) throws WGAPIException {
if (!isroot()) {
return tag.tmlContext.getsessionvar(getVarPrefix() + name);
} else {
return null;
}
}
@CodeCompletion
public void setsessionvar(String name, Object value, boolean allowSerialization) throws WGAPIException {
if (!isroot()) {
tag.tmlContext.setSessionVar(getVarPrefix() + name, value, allowSerialization, true);
}
else {
throw new WGIllegalStateException("Portlet session vars not allowed on root portlet");
}
}
@CodeCompletion
public void removesessionvar(String name) throws WGAPIException {
if (!isroot()) {
tag.tmlContext.removesessionvar(getVarPrefix() + name);
}
}
@CodeCompletion
public void removeitem(String name) throws WGAPIException {
profile.getprofile().getPortletRegistry().removeItem(reg, name);
}
public TMLPortlet getportlet(String key) throws WGAPIException {
WGPortlet portlet = getPortletRegistration(key);
if (portlet != null) {
return new TMLPortlet(tag, profile, portlet);
}
else {
return null;
}
}
public WGPortlet getPortletRegistration(String key) throws WGAPIException {
return profile.getprofile().getPortletRegistry().getPortlet(tag.tmlContext.getmaincontext().db().getDbReference(), key);
}
@CodeCompletion
public TMLPortlet getparentportlet() throws WGAPIException {
if (reg.getParentPortletKey() != null) {
return getportlet(reg.getParentPortletKey());
}
else {
return this;
}
}
public List getchildrenkeys() throws WGAPIException {
Iterator<WGPortlet> childRegs = profile.getprofile().getPortletRegistry().getChildPortlets(reg).iterator();
List childKeys = new ArrayList();
while (childRegs.hasNext()) {
WGPortlet portlet = childRegs.next();
childKeys.add(portlet.getKey());
}
return childKeys;
}
public List getdescendantkeys() throws WGAPIException {
Iterator childRegs = getChildRegistrations(reg, true).iterator();
List childKeys = new ArrayList();
while (childRegs.hasNext()) {
WGPortlet reg = (WGPortlet) childRegs.next();
childKeys.add(reg.getKey());
}
return childKeys;
}
@CodeCompletion
public List getchildrennames() throws WGAPIException {
Iterator<WGPortlet> childRegs = profile.getprofile().getPortletRegistry().getChildPortlets(reg).iterator();
List childKeys = new ArrayList();
while (childRegs.hasNext()) {
WGPortlet reg = (WGPortlet) childRegs.next();
childKeys.add(reg.getName());
}
return childKeys;
}
public List getdescendantnames() throws WGAPIException {
Iterator childRegs = getChildRegistrations(reg, true).iterator();
List childKeys = new ArrayList();
while (childRegs.hasNext()) {
WGPortlet reg = (WGPortlet) childRegs.next();
childKeys.add(reg.getName());
}
return childKeys;
}
// register portlet and update Hashmap
public String registerportlet(String tmlDb, String tmlCode, String title) throws WGAPIException {
WGPortlet newPortlet = profile.getprofile().getPortletRegistry().createPortlet(tag.tmlContext.getmaincontext().db().getDbReference(), reg);
newPortlet.setDesignDb(tmlDb);
newPortlet.setDesign(tmlCode);
newPortlet.setName(title);
profile.getprofile().getPortletRegistry().insertPortlet(newPortlet);
TMLPortlet newTMLPortlet = new TMLPortlet(tag, profile, newPortlet);
// Fetch portlet session context (so the following init event will not be overridden)
newTMLPortlet.getSessionContext();
// Add portlet init event
PortletEvent event = new PortletEvent("init");
event.setSource(newPortlet.getKey());
event.setSourceName(title);
event.setTargetPortletKey(newPortlet.getKey());
addEventToQueue(event, tag.tmlContext.gethttpsession());
return newPortlet.getKey();
}
public String registerportlet(String tmlCode, String title) throws WGAPIException {
return registerportlet(null, tmlCode, title);
}
// unregister portlet and update Hashmap
public boolean unregisterportlet(String portletKey) throws WGAPIException {
TMLPortlet portlet = getportlet(portletKey);
if (portlet == null) {
throw new WGIllegalArgumentException("Unable to unregister portlet with key '" + portletKey + "' - no portlet with this key registered.");
}
portlet.unregister();
return true;
}
public void unregister() throws WGAPIException {
this.profile.getprofile().getPortletRegistry().removePortlet(reg);
cleanupPortletSession();
this.profile.save(true);
}
private List<WGPortlet> getChildRegistrations(WGPortlet parent, boolean descendants) throws WGAPIException {
List<WGPortlet> childRegs = new ArrayList<WGPortlet>();
Iterator<WGPortlet> iter = profile.getprofile().getPortletRegistry().getChildPortlets(parent).iterator();
while (iter.hasNext()) {
WGPortlet child = (WGPortlet) iter.next();
childRegs.add(child);
if (descendants) {
childRegs.addAll(getChildRegistrations(child, true));
}
}
return childRegs;
}
@CodeCompletion
public void cleanup() throws WGAPIException {
this.profile.getprofile().getPortletRegistry().clearItems(reg);
cleanupPortletSession();
}
@CodeCompletion
public void cleanupPortletSession() {
// Remove the session context
String completeKey = getSessionContextKey();
HttpSession session = tag.tmlContext.getEnvironment().getPageContext().getSession();
synchronized (session) {
Map contexts = getPortletSessionContexts(session);
contexts.remove( completeKey );
}
// Remove portlet variables
tag.tmlContext.removePortletVariables(this);
}
public void cleanup(String portletKey) throws WGAPIException {
TMLPortlet portlet = getportlet(portletKey);
if (portlet != null) {
portlet.cleanup();
}
else {
throw new WGIllegalArgumentException("Unable to cleanup portlet with key '" + portletKey + "' - no portlet with this key registered.");
}
}
/**
* Returns the title.
* @return String
*/
public String getportletkey() {
return reg.getKey();
}
public String getparentkey() {
return reg.getParentPortletKey();
}
@CodeCompletion
public String gettml() {
return reg.getDesign();
}
@CodeCompletion
public String gettmldb() {
return reg.getDesignDb();
}
public List getitemnames() throws WGAPIException {
return this.profile.getprofile().getPortletRegistry().getItemNames(reg);
}
public String gettitle() {
return reg.getName();
}
@CodeCompletion
public String getname() {
return reg.getName();
}
/**
* Returns the mode.
* @return String
*/
@CodeCompletion
public String getmode() {
return getSessionContext().getMode();
}
public SessionContext getSessionContext() {
String completeKey = getSessionContextKey();
HttpSession session = tag.tmlContext.gethttpsession();
synchronized (session) {
// Get - conditionally create session context map
Map contexts = getPortletSessionContexts(session);
// Get - conditionally create individual session context
SessionContext context = (SessionContext) contexts.get( completeKey );
if (context == null) {
context = new SessionContext();
// Set event index to current last index, so events fired before creation of this context are not executed for it
LinkedMap list = TMLPortlet.getFiredEventsQueue(session);
if (!list.isEmpty()) {
PortletEvent event = (PortletEvent) list.get(list.lastKey());
context.setLastProcessedEventIndex(event.getIndex());
}
contexts.put(completeKey, context);
}
return context;
}
}
private String getSessionContextKey() {
return profile.getprofile().getDatabase().getDbReference() + "/" + getportletkey();
}
private static Map getPortletSessionContexts(HttpSession session) {
synchronized (session) {
Map contexts = (Map) session.getAttribute(WGACore.SESSION_PORTLETMODES);
if (contexts == null) {
contexts = Collections.synchronizedMap(new HashMap());
session.setAttribute(WGACore.SESSION_PORTLETMODES, contexts);
}
return contexts;
}
}
/**
* Queue that stores the last 1000 fired portlet events, so portlets can
* react on them with serverside code.
* @param session
* @return
*/
public static LinkedMap getFiredEventsQueue(HttpSession session) {
synchronized (session) {
TransientObjectWrapper<LinkedMap> eventWrapper = (TransientObjectWrapper<LinkedMap>) session.getAttribute(WGACore.SESSION_FIREDPORTLETEVENTS);
if (eventWrapper == null || eventWrapper.get() == null) {
eventWrapper = new TransientObjectWrapper<LinkedMap>();
eventWrapper.set(new LinkedMap());
session.setAttribute(WGACore.SESSION_FIREDPORTLETEVENTS, eventWrapper);
}
return eventWrapper.get();
}
}
protected static void addEventToQueue(PortletEvent event, HttpSession session) {
LinkedMap events = getFiredEventsQueue(session);
synchronized (events) {
event.retrieveIndex();
events.put(new Long(event.getIndex()), event);
while (events.size() > EVENTQUEUE_MAX_SIZE) {
events.remove(events.firstKey());
}
}
}
/**
* Sets the mode.
* @param mode The mode to set
*/
@CodeCompletion
public void setmode(String mode) {
getSessionContext().setMode(mode);
}
public void changeDesign(String tml) throws WGAPIException {
reg.setDesign(tml);
this.profile.save(true);
}
public String getDesign() {
return reg.getDesign();
}
@CodeCompletion
public void fireevent(PortletEvent event) {
// store event in hashSet and request for rendering through include tag
HashSet currentEvents = (HashSet) this.tag.tmlContext.getrequest().getAttribute(PORTLET_EVENT_REQUEST_ATTR_PREFIX + this.reg.getKey());
if (currentEvents == null) {
currentEvents = new HashSet();
this.tag.tmlContext.getrequest().setAttribute(PORTLET_EVENT_REQUEST_ATTR_PREFIX + this.reg.getKey(), currentEvents);
}
// set source
event.setSource(getportletkey());
event.setSourceName(getname());
currentEvents.add(event);
// Add to event queue
addEventToQueue(event, this.tag.tmlContext.gethttpsession());
}
@CodeCompletion
public void fireevent(String eventname) {
fireevent(tag.tmlContext.createevent(eventname));
}
@CodeCompletion
public String registerportletforname(String name, String moduleDb, String module, boolean overwrite) throws WGAPIException {
// Verify portlet name rules
if (!PORTLETNAME_PATTERN.matcher(name).matches()) {
throw new WGIllegalArgumentException("The portletname may only consist of characters A-Z, a-z, 0-9, _, ., - and :");
}
// Verify portlet name not already used
TMLPortlet portlet = getportletforname(name);
if (portlet != null) {
if (overwrite) {
unregisterportletforname(name);
}
else {
throw new WGIllegalArgumentException("Portlet name '" + name + "' already registered.");
}
}
String pkey = registerportlet(moduleDb, module, name);
setitem(PORTLETNAME_ITEM_PREFIX + name, pkey);
save();
return pkey;
}
@CodeCompletion
public String registerportletforname(String name, String module, boolean overwrite) throws WGAPIException {
return registerportletforname(name, null, module, overwrite);
}
@CodeCompletion
public String registerportletforname(String name, String module) throws WGAPIException {
return registerportletforname(name, null, module, false);
}
@CodeCompletion
public String registerportletforname(String name, String moduleDb, String module) throws WGAPIException {
return registerportletforname(name, moduleDb, module, false);
}
@CodeCompletion
public TMLPortlet getportletforname(String name) throws WGAPIException {
if (!hasitem(PORTLETNAME_ITEM_PREFIX + name)) {
return null;
}
String pkey = getPortletKeyForName(name);
return getportlet(pkey);
}
public String getPortletKeyForName(String name) throws WGAPIException {
String pkey = (String) item(PORTLETNAME_ITEM_PREFIX + name);
return pkey;
}
@CodeCompletion
public void unregisterportletforname(String name) throws WGAPIException {
String key = getPortletKeyForName(name);
if (key == null) {
throw new WGIllegalArgumentException("No portlet of name '" + name + "' exists in the current portlet scope");
}
unregisterportlet(key);
removeitem(PORTLETNAME_ITEM_PREFIX + name);
save();
}
@CodeCompletion
public void unregisterchildportlets() throws WGAPIException {
Iterator keys = getchildrenkeys().iterator();
while (keys.hasNext()) {
String key = (String) keys.next();
TMLPortlet child = getportlet(key);
if (child != null) {
unregisterportlet(child.getportletkey());
}
}
save();
}
public void prepareEventProcessing(Base tag) {
SessionContext sessionContext = getSessionContext();
LinkedMap list = TMLPortlet.getFiredEventsQueue(tag.getPageContext().getSession());
// Look if the event queue proceeded since the last processed event
if (list.size() > 0) {
PortletEvent lastEvent = (PortletEvent) list.get(list.lastKey());
if (lastEvent != null) {
if (lastEvent.getIndex() > sessionContext.getLastProcessedEventIndex()) {
// Find the start index for processing new events
Long startIndex;
Long lastProcessedIndex = new Long(sessionContext.getLastProcessedEventIndex());
if (list.containsKey(lastProcessedIndex)) {
startIndex = (Long) list.nextKey(lastProcessedIndex);
}
else {
startIndex = (Long) list.firstKey();
}
// Set start index as WebTML option
tag.getStatus().setOption(Base.OPTION_PORTLET_EVENT_STARTINDEX, new Long(sessionContext.getLastProcessedEventIndex()), null);
// Update last processed event index to be the newest event's index
sessionContext.setLastProcessedEventIndex(lastEvent.getIndex());
}
}
}
}
public boolean isroot() {
return (reg.isRoot());
}
@CodeCompletion
public TMLPortlet getroot() throws WGAPIException {
TMLPortlet portlet = this;
while (!portlet.isroot()) {
portlet = portlet.getparentportlet();
}
return portlet;
}
public TMLPortlet parent() throws WGAPIException {
return getparentportlet();
}
public TMLPortlet child(String name) throws WGAPIException {
return getportletforname(name);
}
/**
* returns the session based context used for this portlet
* @return
*/
public TMLContext getcontext() {
if (tag == null) {
return null;
}
if (getSessionContext().getContextPath() != null) {
return tag.tmlContext.context(getSessionContext().getContextPath());
} else {
return null;
}
}
/**
* sets the session based context used for this portlet
* @param context
* @throws WGAPIException
*/
public void setcontext(TMLContext context) throws WGAPIException {
if (tag == null) {
throw new WGIllegalStateException("Portlet context is not supported on none webtml environments.");
} else if (! (tag instanceof Root.Status)) {
throw new WGIllegalStateException("Portlet context cannot be set outside actions.");
}
if (context != null) {
getSessionContext().setContextPath(context.getpath());
} else {
getSessionContext().setContextPath(null);
}
}
public String getVarPrefix() {
return reg.getKey() + "_";
}
@CodeCompletion
public TMLPortlet getsourceportlet(PortletEvent event) throws WGAPIException {
return getportlet(event.getSource());
}
}