de.innovationgate.wgpublisher.webtml.Include.java Source code

Java tutorial

Introduction

Here is the source code for de.innovationgate.wgpublisher.webtml.Include.java

Source

/*******************************************************************************
 * 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;

import java.io.IOException;
import java.io.InputStreamReader;
import java.io.Reader;
import java.io.StringWriter;
import java.net.URL;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;

import javax.servlet.http.HttpServletResponse;
import javax.servlet.jsp.PageContext;
import javax.servlet.jsp.tagext.DynamicAttributes;

import org.apache.commons.httpclient.HttpClient;
import org.apache.commons.httpclient.HttpVersion;
import org.apache.commons.httpclient.URI;
import org.apache.commons.httpclient.methods.GetMethod;
import org.apache.commons.httpclient.params.HttpMethodParams;

import de.innovationgate.utils.URLBuilder;
import de.innovationgate.utils.WGUtils;
import de.innovationgate.webgate.api.WGContent;
import de.innovationgate.webgate.api.WGDatabase;
import de.innovationgate.webgate.api.WGException;
import de.innovationgate.webgate.api.WGFactory;
import de.innovationgate.webgate.api.WGPortlet;
import de.innovationgate.webgate.api.WGTransientPortlet;
import de.innovationgate.wga.server.api.Design;
import de.innovationgate.wga.server.api.WGA;
import de.innovationgate.wgpublisher.design.DesignResourceReference;
import de.innovationgate.wgpublisher.websockets.PageConnection;
import de.innovationgate.wgpublisher.webtml.actions.TMLAction;
import de.innovationgate.wgpublisher.webtml.portlet.TMLPortlet;
import de.innovationgate.wgpublisher.webtml.portlet.TMLPortletStateTransientStorage;
import de.innovationgate.wgpublisher.webtml.utils.AjaxInfo;
import de.innovationgate.wgpublisher.webtml.utils.RootTagReceptor;
import de.innovationgate.wgpublisher.webtml.utils.TMLContext;
import de.innovationgate.wgpublisher.webtml.utils.TMLException;
import de.innovationgate.wgpublisher.webtml.utils.TMLOption;
import de.innovationgate.wgpublisher.webtml.utils.TMLPageImpl;
import de.innovationgate.wgpublisher.webtml.utils.TMLSilentCancelException;

public class Include extends Base implements DynamicAttributes, PreferredOptionReceiverTag {

    /**
      * 
      */
    private static final long serialVersionUID = 1L;

    private String key;
    private String ref;
    private String name;
    private String type;
    private String mediakey;
    private String designdb;
    private String linkaction;
    private String ajax;
    private String keepoptions;
    private String timeout;
    private String encoding;
    private String linkmedium;
    private String tmlscope;
    private String portletmode;
    private String limit;
    private String forcestate;

    public static class Status extends BaseTagStatus implements RootTagReceptor, DirectOutputCapableTag {
        String type;
        String ref;
        String designdb;
        public Root.Status rootTag;
        private Set<String> optionsToFilter;
        boolean refAlreadyResolved = false;

        private long _startTime;
        private boolean _portletInclude;
        boolean ajax;
        private AjaxInfo _ajaxInfo;
        public boolean realDirectOutput;
        public Map<String, Object> includedModuleLocalVars = new HashMap<String, Object>();

        public TMLContext getChildTagContext() {
            return childTMLContext;
        }

        public void setRootTagStatus(Root.Status root) {
            rootTag = root;
        }

        @Override
        public void setOption(String name, Object value, String scope) {
            super.setOption(name, value, scope);

            // Remove options set in include body from options to filter
            if (optionsToFilter.contains(name)) {
                optionsToFilter.remove(name);
            }
        }

        public Set<String> getOptionsToFilter() {
            return optionsToFilter;
        }

        @Override
        public void initAttributeDelegates(Base tag) {

            Include incTag = (Include) tag;

            this.type = incTag.getType();
            this.ref = incTag.getRef();
            this.designdb = incTag.getDesigndb();

            super.initAttributeDelegates(tag);
        }

        public boolean isPortletInclude() {
            return _portletInclude;
        }

        public AjaxInfo getPortletAJAXInfo() {
            return _ajaxInfo;
        }

        @Override
        public de.innovationgate.wgpublisher.webtml.Root.Status getRootTagStatus() {
            return rootTag;
        }

        @Override
        public void setStartTime(long currentTimeMillis) {
            _startTime = currentTimeMillis;
        }

        @Override
        public long getStartTime() {
            return _startTime;
        }

        @Override
        public boolean isDirectOutputCapable() {
            return false;
        }

        @Override
        public Map<String, Object> getLocalVarsToInherit() {
            return includedModuleLocalVars;
        }

    }

    @Override
    public BaseTagStatus createTagStatus() {
        return new Status();
    }

    public static final String TYPE_TML = "tml";
    public static final String TYPE_STATICTML = "statictml";
    public static final String TYPE_INNERLAYOUT = "innerlayout";
    public static final String TYPE_URL = "url";
    public static final String TYPE_PORTLET = "portlet";

    public static final String OPTION_PORTLET_TMLMODULE = SYSTEMOPTION_PREFIX + "portletTmlmodule";
    public static final String OPTION_AJAX_DIVTAG_ID = SYSTEMOPTION_PREFIX + "ajaxDivTagId";

    public void tmlEndTag() throws TMLException, WGException {

        Status status = (Status) getStatus();

        WGDatabase database = this.getMainContext().getdocument().getDatabase();

        // Set WebTML scope option
        String scope = getTmlscope();
        if (scope != null) {
            status.setOption(Base.OPTION_WEBTML_SCOPE, scope, TMLOption.SCOPE_GLOBAL);
        }

        // Set link action option
        String linkAction = this.getLinkaction();
        if (linkAction != null) {
            TMLAction linkActionDef = getTMLContext().getActionByID(linkAction, getTMLContext().getDesignDBKey());
            if (linkActionDef != null) {
                getStatus().setOption(OPTION_LINK_ACTION,
                        new DesignResourceReference(linkActionDef.getModuleDatabase(), linkActionDef.getID()),
                        TMLOption.SCOPE_GLOBAL);
            }
        }

        // Set option "body" by content of include tag
        // (only when no option "body" already available that was set explicitly for this tag or is of global scope)
        TMLOption bodyOption = (TMLOption) getStatus().getTagOptions().get("body");
        if (bodyOption == null || (status.optionsToFilter.contains("body")
                && bodyOption.getScope().equals(TMLOption.SCOPE_LOCAL))) {
            status.setOption("body", getResultString(), TMLOption.SCOPE_LOCAL);
            status.optionsToFilter.remove("body");
        }

        // Prepare portlet inclusion
        if (status.type.equals(TYPE_PORTLET)) {
            preparePortletInclusion();

            // The portlet inclusion is internally handled like a normal TML module inclusion
            performTMLInclude(TYPE_TML, database);
        }

        // Perform individual include type
        else if (status.type.equals(TYPE_TML) || status.type.equals(TYPE_INNERLAYOUT)) {
            performTMLInclude(status.type, database);
        }

        else if (status.type.equals(TYPE_STATICTML)) {
            performStaticTMLInclude();
        }

        else if (status.type.equals(TYPE_URL)) {
            performURLInclude();
        }

    }

    private void performURLInclude() throws TMLException {

        Status status = (Status) getStatus();

        try {

            HttpClient client = WGFactory.getHttpClientFactory().createHttpClient();
            HttpMethodParams methodParams = new HttpMethodParams();
            methodParams.setSoTimeout(Integer.parseInt(getTimeout()));
            methodParams.setVersion(HttpVersion.HTTP_1_1);

            GetMethod getMethod = new GetMethod();
            getMethod.setURI(new URI(status.ref, false));
            getMethod.setParams(methodParams);
            getMethod.setFollowRedirects(true);

            int httpStatus = client.executeMethod(getMethod);
            if (httpStatus != HttpServletResponse.SC_OK) {
                throw new TMLException("Response status " + httpStatus + " (" + getMethod.getStatusText()
                        + ") for included URL " + ref, true);
            }

            String encoding = getEncoding();
            if (encoding == null) {
                encoding = getMethod.getResponseCharSet();
                if (encoding == null) {
                    getTMLContext().addwarning("No encoding returned from URL '" + status.ref
                            + "'. Assuming default encoding " + encoding);
                    encoding = getCore().getCharacterEncoding();
                }
            }

            Reader reader = new InputStreamReader(getMethod.getResponseBodyAsStream(), encoding);
            StringWriter writer = new StringWriter();
            char[] buf = new char[2048];
            long count = 0;

            String limitStr = getLimit();
            long charLimit = 0;
            try {
                charLimit = Math.round(1024 * 1024 * Double.parseDouble(limitStr));
            } catch (NumberFormatException e) {
                throw new TMLException("Cannot parse limit attribute as number: " + limitStr);
            }

            int len;
            while ((len = reader.read(buf)) != -1) {
                writer.write(buf, 0, len);
                count += len;
                if (charLimit != 0 && count > charLimit) {
                    throw new TMLException("Include of URL '" + status.ref + "' reaches content limit of "
                            + limitStr + " million characters. Include is cancelled.");
                }
            }
            this.setResult(writer.toString());

        } catch (java.io.IOException exc) {
            log.error("Exception including url", exc);
            this.addWarning("Exception while including url: " + exc.getMessage());
        }

    }

    private void performStaticTMLInclude() throws TMLException {

        Status status = (Status) getStatus();

        // Locate resource
        String tmlName = status.ref;
        if (tmlName == null || tmlName.equals("")) {
            throw new TMLException("Could not retrieve result of included static resource. No reference given.");
        }

        String jspPath = "/static/tml/" + tmlName + ".jsp";

        // Perform include
        String oldDesignDB = (String) getOption(Base.OPTION_DESIGNDB);
        this.pageContext.setAttribute(ATTRIB_INCLUDEPARENT, status, PageContext.REQUEST_SCOPE);
        getStatus().setOption(Base.OPTION_DESIGNDB, oldDesignDB, null);
        try {
            this.pageContext.include(jspPath);
        } catch (Exception e) {
            throw new TMLException("Error executing static tml module \"" + tmlName + "\"", e, false);
        } finally {
            this.pageContext.setAttribute(ATTRIB_INCLUDEPARENT, null, PageContext.REQUEST_SCOPE);
            getStatus().setOption(Base.OPTION_DESIGNDB, oldDesignDB, null);
        }

        // Import the included root tag result
        if (status.rootTag != null) {
            this.setResult(status.rootTag.result);
            status.rootTag.result = null;
        } else {
            throw new TMLException("Could not retrieve result of included static resource: " + tmlName);
        }

    }

    private void performTMLInclude(String type, WGDatabase database) throws WGException, TMLException {

        Status status = (Status) getStatus();

        String mediaKey = this.getMedium();
        if (mediaKey == null) {
            mediaKey = status.getTMLModuleMediaKey();
        }

        // Fetch the resource to include
        DesignResourceReference includeReference;
        WGA wga = WGA.get(getTMLContext());
        if (type.equals(TYPE_INNERLAYOUT)) {

            // Optional inner layout override via option
            if (getTMLContext().hasoption(OPTION_INNER_LAYOUT)) {
                Object innerLayout = getTMLContext().option(OPTION_INNER_LAYOUT);
                if (innerLayout instanceof Inline.Pointer) {
                    performInlineInclude((Inline.Pointer) innerLayout);
                } else {
                    this.setResult(String.valueOf(innerLayout));
                }
                return;
            }

            WGContent currentContent = this.getTMLContext().content();
            if (currentContent.isDummy() || !currentContent.hasCompleteRelationships()) {
                throw new TMLException(
                        "The inner layout of this content cannot be determined, bc. it is either a dummy document, or it doesnt have complete document relationships.");
            }
            Design databaseDesign = wga.design(getTMLContext().content().getDatabase());
            includeReference = databaseDesign
                    .resolve(currentContent.getStructEntry().getContentType().getInnerLayoutName())
                    .getBaseReference();
            status.designdb = includeReference.getDesignApp();
        }

        else {
            WGDatabase designdb = database;
            DesignResourceReference refObj;
            if (status.refAlreadyResolved) {
                includeReference = new DesignResourceReference(status.designdb, status.ref);
            } else {
                includeReference = getTMLContext().resolveDesignResource(status.designdb, status.ref);
            }
        }

        // Got to hijack direct output a bit here. If our parent tag has direct output
        // The included root may have as well. Only the include itself should not have it
        // as its body is needed for option "body".
        if (maySwitchToDirectOutputForInclude(status)) {
            status.directOutput = true;
        }

        if (status.directOutput) { // Must be done here bc. the prefix was modified by preparePortletInclusion() after it was already put out on tag start
            try {
                pageContext.getOut().write(getPrefix());
            } catch (IOException e) {
                throw new WGException("Exception writing include prefix", e);
            }
        }

        // Dispatch
        status.result = null;
        getCore().getDispatcher().dispatchToRenderer(wga,
                wga.design(includeReference.getDesignApp()).resolve(includeReference.getResourceName()),
                status.childTMLContext, mediaKey);

        if (status.directOutput) { // Must be done here bc. the suffix will not be put out automatically after we switch off directOutput as tag result is null.
            try {
                pageContext.getOut().write(getSuffix());
                setResult(null);
            } catch (IOException e) {
                throw new WGException("Exception writing include prefix", e);
            }
        }

        status.directOutput = false;

    }

    private boolean maySwitchToDirectOutputForInclude(Status status) {
        if (!status.getParentTag().directOutput) {
            return false;
        }

        if (isVarWritten()) {
            return false;
        }

        return true;
    }

    private void preparePortletInclusion() throws TMLException, WGException {

        Status status = (Status) getStatus();
        status._portletInclude = true;

        String type;
        TMLPortlet parentPortlet = getTMLContext().getportlet();
        if (parentPortlet == null) {
            if (getTMLContext().isbotrequest()) {
                throw new TMLSilentCancelException();
            } else {
                throw new TMLException("Current user has no portlet registration");
            }
        }

        String pKey = getKey();
        String pName = getName();

        //  If no key direcly given try to fetch via portlet name
        WGPortlet pReg = null;
        if (pName != null) {
            TMLPortlet tmlPortlet = parentPortlet.getportletforname(pName);
            if (tmlPortlet != null) {
                pReg = tmlPortlet.getReg();
                pKey = tmlPortlet.getportletkey();
            }
        }

        // Fetch registration info if key provided
        else if (pKey != null) {
            pReg = parentPortlet.getPortletRegistration(pKey);
        }

        // If reg not retrievable and auto registration info available try auto registration, else cancel
        if (pReg == null) {
            if (pName != null && status.ref != null) {
                DesignResourceReference tagRef = getTMLContext().resolveDesignResource(status.designdb, status.ref);
                pKey = parentPortlet.registerportletforname(pName, tagRef, false);
                pReg = parentPortlet.getPortletRegistration(pKey);
            } else {
                if (pName != null) {
                    throw new TMLException("Portlet name not registered: " + pName, true);
                } else if (pKey != null) {
                    throw new TMLException("Portlet key not registered: " + pKey, true);
                } else {
                    throw new TMLException("No portlet key provided.", true);
                }
            }
        }

        // Check if registration matchtes the autoregister TML. If not, reregister with the given TML.
        else if (status.ref != null) {

            DesignResourceReference tagRef = getTMLContext().resolveDesignResource(status.designdb, status.ref);

            // Design references in the portlet registry should already be resolved. Just expanding local references for backward compatibility (#00001674)
            DesignResourceReference portletRef = new DesignResourceReference(pReg.getDesignDb(),
                    TMLContext.processReferencePath(pReg.getDesign(),
                            getTMLContext().getDesignContext().getBaseReference().getResourceName()));

            // If the registration has no design database specified we take the current design db (which is always equal in the following test)
            if (portletRef != null && portletRef.getDesignApp() == null) {
                portletRef.setDesignApp(status.designdb);
            }

            if (!WGUtils.nullSafeEquals(tagRef.getResourceName(), portletRef.getResourceName(), true)
                    || !WGUtils.nullSafeEquals(tagRef.getDesignApp(), portletRef.getDesignApp(), true)) {
                if (pReg instanceof WGTransientPortlet) { // On transient portlets just write the info to prevent re-registration (#00004253)
                    WGTransientPortlet transientReg = (WGTransientPortlet) pReg;
                    pReg.setDesignDb(tagRef.getDesignApp());
                    pReg.setDesign(tagRef.getResourceName());
                } else {
                    pKey = parentPortlet.registerportletforname(pName, tagRef, true);
                    pReg = parentPortlet.getPortletRegistration(pKey);
                }
            }

        }

        // Set portlet namespace option, setting this portlet for WebTML
        status.setOption(Base.OPTION_PORTLET_NAMESPACE, pKey, null);

        // Prepare environment. Load back portlet registration info into tag attribute delegates
        if (pReg.getDesignDb() != null) {
            status.designdb = pReg.getDesignDb();
        }
        status.ref = pReg.getDesign();
        if (status.ref != null && !status.ref.startsWith("::")) { // Backward compatibility with old registrations, which may have made it into registry unresolved (see #00001574) 
            status.refAlreadyResolved = true;
        }

        // Work on the TMLScript portlet object from now on
        TMLPortlet portlet = getTMLContext().getportlet();

        // Build basic HTML structures for AJAX processing
        String portletMode = getPortletmode();
        if (status.ajax) {
            createAJAXHTML(status, portlet, (portletMode != null ? portletMode : "view"));
        }

        // If an initial mode is determined set it
        if (portletMode != null) {
            portlet.setmode(portletMode);
            portlet.forcestate();
        }

        if (stringToBoolean(getForcestate()))
            portlet.forcestate();

        // Instantiate the controller, if available
        portlet.getState();
    }

    private void createAJAXHTML(Status status, TMLPortlet portlet, String initialMode) throws WGException {
        String uniquePortletID = portlet.getportletkey();

        // create prefix buffer
        StringBuffer prefix = new StringBuffer();
        // create suffix buffer
        StringBuffer suffix = new StringBuffer();

        // set id as option, so tags from included module can retrieve it for rendering ajaxCall
        status.setOption(OPTION_AJAX_DIVTAG_ID, uniquePortletID, null);
        // set tmlModule as option, so tags from included module can retrieve the tmlmodulename
        status.setOption(OPTION_PORTLET_TMLMODULE, portlet.getDesign(), null);

        // create ajaxInfo
        AjaxInfo ajaxInfo = new AjaxInfo(uniquePortletID);
        try {
            URLBuilder builder = new URLBuilder(getTMLContext().getrequest(), getCore().getCharacterEncoding());
            builder.removeParameter("$action");
            java.net.URL url = new URL(builder.build(true));
            ajaxInfo.setQueryString(url.getQuery());
        } catch (Exception e1) {
            getTMLContext().addwarning("Unable to build request querystring for ajax environment.");
        }
        ajaxInfo.setPortletPath(portlet.getportletpath());
        ajaxInfo.setTmlmodule(portlet.getDesign());
        ajaxInfo.setDesignDB(portlet.gettmldb() != null ? portlet.gettmldb() : getDesigndb());
        ajaxInfo.setMediaKey((String) this.getOption(OPTION_CURRENT_MEDIAKEY));
        ajaxInfo.setContextPath(this.getTMLContext().getpath());

        ajaxInfo.setOptions(new HashMap<String, TMLOption>(getStatus().getTagOptions()));
        ajaxInfo.getOptions().keySet().removeAll(Root.UNRECOVERABLE_OPTIONS);

        ajaxInfo.setProfileSessionId(getTMLContext().getmaincontext().getprofile().getProfileSessionId());
        ajaxInfo.setInitialMode(initialMode);
        if (getTMLContext().getprofile() != null) {
            ajaxInfo.setSaveProfileOnEnd(getTMLContext().getprofile().isSavedOnEnd());
        } else {
            ajaxInfo.setSaveProfileOnEnd(false);
        }
        ajaxInfo.setSuperform(getStatus().getRelevantForm());

        TMLPageImpl page = (TMLPageImpl) WGA.get(getTMLContext()).tmlPage();
        PageConnection pc = page.getPageConnection(false);
        if (pc != null) {
            ajaxInfo.setPageId(pc.getPageId());
        }

        status._ajaxInfo = ajaxInfo;

        // create javascript object with ajaxInfo                    
        // serialize        
        String serAjaxInfo = getCore().getDefaultSerializer().toXML(ajaxInfo);
        // zip
        byte[] zipped = WGUtils.zipString(serAjaxInfo);
        String encryptedAjaxInfo = "";
        if (serAjaxInfo != null) {
            // encrypt
            try {
                encryptedAjaxInfo = this.getTMLContext().getwgacore().getSymmetricEncryptionEngine()
                        .encryptBase64Web(zipped);
            } catch (Exception e) {
                throw new TMLException(
                        "Cannot render ajax enabled include because of unsupported encoding: " + e.getMessage(),
                        true);
            }
        }

        // div tag for ajax paste
        prefix.append("<div id=\"$ajaxDiv_").append(uniquePortletID).append("\">");
        prefix.append("<div id=\"$ajaxContentDiv_").append(uniquePortletID).append("\">");

        prefix.append("<script>");

        // javascript variable ajaxInfo
        prefix.append("var $ajaxInfo_").append(uniquePortletID).append(" = '").append(encryptedAjaxInfo + "';");

        prefix.append("</script>");
        suffix.append("</div></div>");

        // set prefix and suffix
        this.setPrefix(prefix.toString());
        this.setSuffix(suffix.toString());
    }

    public String getKey() {
        return this.getTagAttributeValue("key", this.key, null);
    }

    public void setKey(String name) {
        this.key = name;
    }

    /**
     * Gets the name
     * @return Returns a String
     */
    public String getRef() {
        return this.getTagAttributeValue("ref", ref, null);
    }

    /**
     * Sets the name
     * @param name The name to set
     */
    public void setRef(String name) {
        this.ref = name;
    }

    /**
     * Gets the type
     * @return Returns a String
     */
    public String getType() {
        return this.getTagAttributeValue("type", this.type, "tml");
    }

    /**
     * Sets the type
     * @param type The type to set
     */
    public void setType(String type) {
        this.type = type;
    }

    /**
     * Gets the mediakey
     * @return Returns a String
     */
    public String getMedium() {
        return this.getTagAttributeValue("medium", mediakey, null);
    }

    /**
     * Sets the mediakey
     * @param mediakey The mediakey to set
     */
    public void setMedium(String mediakey) {
        this.mediakey = mediakey;
    }

    /**
     * Returns the designdb.
     * @return String
     */
    public String getDesigndb() {

        return this.getTagAttributeValue("designdb", designdb, getDesignDBKey());

    }

    /**
     * Sets the designdb.
     * @param designdb The designdb to set
     */
    public void setDesigndb(String designdb) {
        this.designdb = designdb;
    }

    /**
     * Returns the linkaction.
     * @return String
     */
    public String getLinkaction() {
        return this.getTagAttributeValue("linkaction", linkaction, null);
    }

    /**
     * Sets the linkaction.
     * @param linkaction The linkaction to set
     */
    public void setLinkaction(String linkaction) {
        this.linkaction = linkaction;
    }

    private String getAjax() {
        return this.getTagAttributeValue("ajax", ajax, "false");
    }

    public void setAjax(String ajax) {
        this.ajax = ajax;
    }

    public boolean isAjaxCall() {
        if (getAjax().equalsIgnoreCase(Base.AJAX_MODE_NO_PORTLET_REFRESH)) {
            return true;
        } else {
            return stringToBoolean(getAjax());
        }
    }

    public String getKeepoptions() {
        return getTagAttributeValue("keepoptions", keepoptions, null);
    }

    public void setKeepoptions(String keepoptions) {
        this.keepoptions = keepoptions;
    }

    public void tmlStartTag() throws WGException {
        Status status = (Status) getStatus();
        status._startTime = System.currentTimeMillis();
        status.ajax = stringToBoolean(getAjax());
        if (getTMLContext().getPortletStateStorage() instanceof TMLPortletStateTransientStorage) {
            status.ajax = true;
        }

        // Keep names of options already available at start of include tag
        // to be able to differ them from options set inside

        // Build set of options to test for scope in included Root-Tag.
        // Options to test are
        // a) inherited from earlier tags (not set inside include tag)
        // b) not included in attribute keepoptions

        status.optionsToFilter = new HashSet<String>(getStatus().getTagOptions().keySet());

        String keepOptionsStr = getKeepoptions();
        List<String> keepOptions;
        if (keepOptionsStr != null) {
            keepOptions = WGUtils.deserializeCollection(keepOptionsStr, ",", true);
        } else {
            keepOptions = new ArrayList<String>();
        }
        status.optionsToFilter.removeAll(keepOptions);

        // Set options injected by dynamic JSP attributes
        for (DynamicAttribute att : status.dynamicOptions.values()) {
            if (att.getPrefix().equals("o")) {
                Object optionValue = att.getDynamicValue(getTMLContext());
                if (optionValue != null) {
                    status.setOption(att.getBaseName(), optionValue, TMLOption.SCOPE_GLOBAL);
                    if (status.optionsToFilter.contains(att.getBaseName())) {
                        status.optionsToFilter.remove(att.getBaseName());
                    }
                }
            }
        }

        // Set options by attributes
        String linkMedium = getLinkmedium();
        if (linkMedium != null) {
            status.setOption(OPTION_LINK_MEDIUM, linkMedium, TMLOption.SCOPE_GLOBAL);
        }

        status.dynamicOptions.clear();

    }

    public String getName() {
        return getTagAttributeValue("name", name, null);
    }

    public void setName(String name) {
        this.name = name;
    }

    @Override
    protected List<String> getDynamicAttributePrefixes() {
        return WGUtils.list(super.getDynamicAttributePrefixes(), "o");
    }

    public String getTimeout() {
        return getTagAttributeValue("timeout", timeout, "10000");
    }

    public void setTimeout(String timeout) {
        this.timeout = timeout;
    }

    public String getEncoding() {
        return getTagAttributeValue("encoding", encoding, null);
    }

    public void setEncoding(String encoding) {
        this.encoding = encoding;
    }

    public String getLinkmedium() {
        return getTagAttributeValue("linkmedium", linkmedium, null);
    }

    public void setLinkmedium(String linkmedium) {
        this.linkmedium = linkmedium;
    }

    public String getTmlscope() {
        return getTagAttributeValue("tmlscope", tmlscope, null);
    }

    public void setTmlscope(String tmlscope) {
        this.tmlscope = tmlscope;
    }

    public String getPortletmode() {
        return getTagAttributeValue("portletmode", portletmode, null);
    }

    public void setPortletmode(String portletmode) {
        this.portletmode = portletmode;
    }

    public String getLimit() {
        return getTagAttributeValue("limit", limit, "10");
    }

    public void setLimit(String limit) {
        this.limit = limit;
    }

    public String getForcestate() {
        return getTagAttributeValue("forcestate", forcestate, "false");
    }

    public void setForcestate(String forcestate) {
        this.forcestate = forcestate;
    }

}