org.apereo.portal.portlets.portletadmin.PortletAdministrationHelper.java Source code

Java tutorial

Introduction

Here is the source code for org.apereo.portal.portlets.portletadmin.PortletAdministrationHelper.java

Source

/**
 * Licensed to Apereo under one or more contributor license
 * agreements. See the NOTICE file distributed with this work
 * for additional information regarding copyright ownership.
 * Apereo licenses this file to you under the Apache License,
 * Version 2.0 (the "License"); you may not use this file
 * except in compliance with the License.  You may obtain a
 * copy of the License at the following location:
 *
 *   http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing,
 * software distributed under the License is distributed on an
 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 * KIND, either express or implied.  See the License for the
 * specific language governing permissions and limitations
 * under the License.
 */
package org.apereo.portal.portlets.portletadmin;

import java.io.IOException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Date;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import java.util.SortedSet;
import java.util.TreeSet;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import javax.portlet.ActionRequest;
import javax.portlet.ActionResponse;
import javax.portlet.PortletMode;
import javax.portlet.PortletRequest;
import javax.portlet.PortletSession;
import javax.portlet.WindowState;
import javax.servlet.ServletContext;
import javax.servlet.http.HttpServletRequest;
import javax.xml.bind.JAXBElement;

import org.apache.commons.lang.StringUtils;
import org.apache.commons.lang3.Validate;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.apache.pluto.container.PortletContainerException;
import org.apache.pluto.container.driver.PortalDriverContainerServices;
import org.apache.pluto.container.driver.PortletRegistryService;
import org.apache.pluto.container.om.portlet.DisplayName;
import org.apache.pluto.container.om.portlet.PortletApplicationDefinition;
import org.apache.pluto.container.om.portlet.PortletDefinition;
import org.apache.pluto.container.om.portlet.Supports;
import org.apereo.portal.EntityIdentifier;
import org.apereo.portal.api.portlet.DelegateState;
import org.apereo.portal.api.portlet.DelegationActionResponse;
import org.apereo.portal.api.portlet.PortletDelegationDispatcher;
import org.apereo.portal.api.portlet.PortletDelegationLocator;
import org.apereo.portal.channel.IPortletPublishingService;
import org.apereo.portal.groups.IEntityGroup;
import org.apereo.portal.groups.IGroupMember;
import org.apereo.portal.layout.dlm.remoting.IGroupListHelper;
import org.apereo.portal.layout.dlm.remoting.JsonEntityBean;
import org.apereo.portal.portlet.PortletUtils;
import org.apereo.portal.portlet.dao.jpa.PortletDefinitionImpl;
import org.apereo.portal.portlet.dao.jpa.PortletPreferenceImpl;
import org.apereo.portal.portlet.delegation.jsp.RenderPortletTag;
import org.apereo.portal.portlet.om.IPortletDefinition;
import org.apereo.portal.portlet.om.IPortletPreference;
import org.apereo.portal.portlet.om.IPortletType;
import org.apereo.portal.portlet.om.IPortletWindowId;
import org.apereo.portal.portlet.om.PortletCategory;
import org.apereo.portal.portlet.om.PortletLifecycleState;
import org.apereo.portal.portlet.registry.IPortletCategoryRegistry;
import org.apereo.portal.portlet.registry.IPortletDefinitionRegistry;
import org.apereo.portal.portlet.registry.IPortletTypeRegistry;
import org.apereo.portal.portlet.rendering.IPortletRenderer;
import org.apereo.portal.portletpublishing.xml.MultiValuedPreferenceInputType;
import org.apereo.portal.portletpublishing.xml.Parameter;
import org.apereo.portal.portletpublishing.xml.ParameterInputType;
import org.apereo.portal.portletpublishing.xml.PortletPublishingDefinition;
import org.apereo.portal.portletpublishing.xml.Preference;
import org.apereo.portal.portletpublishing.xml.PreferenceInputType;
import org.apereo.portal.portletpublishing.xml.SingleValuedPreferenceInputType;
import org.apereo.portal.portletpublishing.xml.Step;
import org.apereo.portal.portlets.Attribute;
import org.apereo.portal.portlets.BooleanAttribute;
import org.apereo.portal.portlets.StringListAttribute;
import org.apereo.portal.portlets.fragmentadmin.FragmentAdministrationHelper;
import org.apereo.portal.portlets.groupselector.EntityEnum;
import org.apereo.portal.portlets.portletadmin.xmlsupport.IChannelPublishingDefinitionDao;
import org.apereo.portal.security.AuthorizationPrincipalHelper;
import org.apereo.portal.security.IAuthorizationPrincipal;
import org.apereo.portal.security.IAuthorizationService;
import org.apereo.portal.security.IPermission;
import org.apereo.portal.security.IPermissionManager;
import org.apereo.portal.security.IPerson;
import org.apereo.portal.security.IUpdatingPermissionManager;
import org.apereo.portal.security.PermissionHelper;
import org.apereo.portal.services.GroupService;
import org.apereo.portal.url.IPortalUrlBuilder;
import org.apereo.portal.url.IPortalUrlProvider;
import org.apereo.portal.url.IPortletUrlBuilder;
import org.apereo.portal.url.UrlType;
import org.apereo.portal.utils.ComparableExtractingComparator;
import org.apereo.portal.utils.Tuple;
import org.apereo.portal.xml.PortletDescriptor;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.web.context.ServletContextAware;
import org.springframework.webflow.context.ExternalContext;

/**
 * Helper methods for the portlet administration workflow.
 *
 * @author Jen Bourey, jbourey@unicon.net
 */
@Service
public final class PortletAdministrationHelper implements ServletContextAware {

    private final Log logger = LogFactory.getLog(this.getClass());

    private static final String PORTLET_FNAME_FRAGMENT_ADMIN_PORTLET = "fragment-admin";

    public static final String[] PORTLET_SUBSCRIBE_ACTIVITIES = { IPermission.PORTLET_SUBSCRIBER_ACTIVITY,
            IPermission.PORTLET_BROWSE_ACTIVITY };

    /*
     * Autowired beans listed alphabetically by type
     */
    @Autowired
    private FragmentAdministrationHelper fragmentAdminHelper;
    @Autowired
    private IAuthorizationService authorizationService;
    @Autowired
    private IChannelPublishingDefinitionDao portletPublishingDefinitionDao;
    @Autowired
    private IGroupListHelper groupListHelper;
    @Autowired
    private IPortalUrlProvider urlProvider;
    @Autowired
    private IPortletCategoryRegistry portletCategoryRegistry;
    @Autowired
    private IPortletDefinitionRegistry portletDefinitionRegistry;
    @Autowired
    private IPortletPublishingService portletPublishingService;
    @Autowired
    private IPortletTypeRegistry portletTypeRegistry;
    @Autowired
    private PortalDriverContainerServices portalDriverContainerServices;
    @Autowired
    private PortletDelegationLocator portletDelegationLocator;

    private ServletContext servletContext;

    @Override
    public void setServletContext(ServletContext servletContext) {
        this.servletContext = servletContext;
    }

    /**
     * Construct a new PortletDefinitionForm for the given IPortletDefinition id.
     * If a PortletDefinition matching this ID already exists, the form will
     * be pre-populated with the PortletDefinition's current configuration.  If
     * the PortletDefinition does not yet exist, a new default form will be
     * created.
     *
     * @param person        user that is required to have related lifecycle permission
     * @param portletId     identifier for the portlet definition
     * @return              {@PortletDefinitionForm} with set values based on portlet definition
     *                      or default category and principal if no definition is found
     */
    public PortletDefinitionForm createPortletDefinitionForm(IPerson person, String portletId) {

        IPortletDefinition def = portletDefinitionRegistry.getPortletDefinition(portletId);

        // create the new form
        final PortletDefinitionForm form;
        if (def != null) {
            // if this is a pre-existing portlet, set the category and permissions
            form = new PortletDefinitionForm(def);
            form.setId(def.getPortletDefinitionId().getStringId());

            // create a JsonEntityBean for each current category and add it
            // to our form bean's category list
            Set<PortletCategory> categories = portletCategoryRegistry.getParentCategories(def);
            for (PortletCategory cat : categories) {
                form.addCategory(new JsonEntityBean(cat));
            }

            addSubscribePermissionsToForm(def, form);
        } else {
            form = createNewPortletDefinitionForm();
        }

        /* TODO:  Service-Layer Security Reboot (great need of refactoring with a community-approved plan in place) */
        // User must have SOME FORM of lifecycle permission over AT LEAST ONE
        // category in which this portlet resides;  lifecycle permissions are
        // hierarchical, so we'll test with the weakest.
        if (!hasLifecyclePermission(person, PortletLifecycleState.CREATED, form.getCategories())) {
            logger.warn("User '" + person.getUserName()
                    + "' attempted to edit the following portlet without MANAGE permission:  " + def);
            throw new SecurityException("Not Authorized");
        }

        return form;
    }

    /*
     * Add to the form SUBSCRIBE and BROWSE activity permissions, along with their principals,
     * assigned to the portlet.
     */
    private void addSubscribePermissionsToForm(IPortletDefinition def, PortletDefinitionForm form) {
        final String portletTargetId = PermissionHelper.permissionTargetIdForPortletDefinition(def);

        /* We are concerned with PORTAL_SUBSCRIBE system */
        final IPermissionManager pm = authorizationService.newPermissionManager(IPermission.PORTAL_SUBSCRIBE);
        for (String activity : PORTLET_SUBSCRIBE_ACTIVITIES) {
            /* Obtain the principals that have permission for the activity on this portlet */
            final IAuthorizationPrincipal[] principals = pm.getAuthorizedPrincipals(activity, portletTargetId);

            for (IAuthorizationPrincipal principal : principals) {
                JsonEntityBean principalBean;

                // first assume this is a group
                IEntityGroup group = GroupService.findGroup(principal.getKey());
                if (group != null) {
                    // principal is a group
                    principalBean = new JsonEntityBean(group, EntityEnum.GROUP);
                } else {
                    // not a group, so it must be a person
                    IGroupMember member = authorizationService.getGroupMember(principal);
                    principalBean = new JsonEntityBean(member, EntityEnum.PERSON);
                    // set the name
                    String name = groupListHelper.lookupEntityName(principalBean);
                    principalBean.setName(name);
                }

                /* Make sure we capture the principal just once*/
                if (!form.getPrincipals().contains(principalBean)) {
                    form.addPrincipal(principalBean);
                }

                form.addPermission(principalBean.getTypeAndIdHash() + "_" + activity);
            }
        }
    }

    /*
     * Create a {@code PortletDefinitionForm} and pre-populate it with default categories and principal permissions.
     */
    private PortletDefinitionForm createNewPortletDefinitionForm() {
        PortletDefinitionForm form = new PortletDefinitionForm();

        // pre-populate with top-level category
        final IEntityGroup portletCategoriesGroup = GroupService
                .getDistinguishedGroup(GroupService.PORTLET_CATEGORIES);
        form.addCategory(
                new JsonEntityBean(portletCategoriesGroup, groupListHelper.getEntityType(portletCategoriesGroup)));

        // pre-populate with top-level group
        final IEntityGroup everyoneGroup = GroupService.getDistinguishedGroup(GroupService.EVERYONE);
        JsonEntityBean everyoneBean = new JsonEntityBean(everyoneGroup,
                groupListHelper.getEntityType(everyoneGroup));
        form.addPrincipal(everyoneBean);
        for (String activity : PORTLET_SUBSCRIBE_ACTIVITIES) {
            form.addPermission(everyoneBean.getTypeAndIdHash() + "_" + activity);
        }
        return form;
    }

    /**
     * Persist a new or edited PortletDefinition from a form, replacing existing values.
     *
     * @param publisher     {@code IPerson} that requires permission to save this definition
     * @param form          form data to persist
     * @return              new {@code PortletDefinitionForm} for this portlet ID
     */
    public PortletDefinitionForm savePortletRegistration(IPerson publisher, PortletDefinitionForm form)
            throws Exception {

        /* TODO:  Service-Layer Security Reboot (great need of refactoring with a community-approved plan in place) */

        // User must have the selected lifecycle permission over AT LEAST ONE
        // category in which this portlet resides.  (This is the same check that
        // is made when the user enters the lifecycle-selection step in the wizard.)
        if (!hasLifecyclePermission(publisher, form.getLifecycleState(), form.getCategories())) {
            logger.warn("User '" + publisher.getUserName()
                    + "' attempted to save the following portlet without the selected MANAGE permission:  " + form);
            throw new SecurityException("Not Authorized");
        }

        if (!form.isNew()) {
            // User must have the previous lifecycle permission
            // in AT LEAST ONE previous category as well
            IPortletDefinition def = this.portletDefinitionRegistry.getPortletDefinition(form.getId());
            Set<PortletCategory> categories = portletCategoryRegistry.getParentCategories(def);
            SortedSet<JsonEntityBean> categoryBeans = new TreeSet<>();
            for (PortletCategory cat : categories) {
                categoryBeans.add(new JsonEntityBean(cat));
            }
            if (!hasLifecyclePermission(publisher, def.getLifecycleState(), categoryBeans)) {
                logger.warn("User '" + publisher.getUserName()
                        + "' attempted to save the following portlet without the previous MANAGE permission:  "
                        + form);
                throw new SecurityException("Not Authorized");
            }
        }

        if (form.isNew() || portletDefinitionRegistry.getPortletDefinition(form.getId()).getType().getId() != form
                .getTypeId()) {
            // User must have access to the selected CPD if s/he selected it in this interaction
            final int selectedTypeId = form.getTypeId();
            final PortletPublishingDefinition cpd = portletPublishingDefinitionDao
                    .getChannelPublishingDefinition(selectedTypeId);
            final Map<IPortletType, PortletPublishingDefinition> allowableCpds = this
                    .getAllowableChannelPublishingDefinitions(publisher);
            if (!allowableCpds.containsValue(cpd)) {
                logger.warn("User '" + publisher.getUserName()
                        + "' attempted to administer the following portlet without the selected "
                        + IPermission.PORTLET_MANAGER_SELECT_PORTLET_TYPE + " permission:  " + form);
                throw new SecurityException("Not Authorized");
            }
        }

        // create the principal array from the form's principal list -- only principals with permissions
        final Set<IGroupMember> subscribePrincipalSet = new HashSet<>(form.getPrincipals().size());
        final Set<IGroupMember> browsePrincipalSet = new HashSet<>(form.getPrincipals().size());
        for (JsonEntityBean bean : form.getPrincipals()) {
            final String subscribePerm = bean.getTypeAndIdHash() + "_" + IPermission.PORTLET_SUBSCRIBER_ACTIVITY;
            final String browsePerm = bean.getTypeAndIdHash() + "_" + IPermission.PORTLET_BROWSE_ACTIVITY;
            final EntityEnum entityEnum = bean.getEntityType();
            final IGroupMember principal = entityEnum.isGroup() ? (GroupService.findGroup(bean.getId()))
                    : (GroupService.getGroupMember(bean.getId(), entityEnum.getClazz()));
            if (form.getPermissions().contains(subscribePerm)) {
                subscribePrincipalSet.add(principal);
            }
            if (form.getPermissions().contains(browsePerm)) {
                browsePrincipalSet.add(principal);
            }
        }

        // create the category list from the form's category bean list
        List<PortletCategory> categories = new ArrayList<>();
        for (JsonEntityBean category : form.getCategories()) {
            String id = category.getId();
            String iCatID = id.startsWith("cat") ? id.substring(3) : id;
            categories.add(portletCategoryRegistry.getPortletCategory(iCatID));
        }

        final IPortletType portletType = portletTypeRegistry.getPortletType(form.getTypeId());
        if (portletType == null) {
            throw new IllegalArgumentException("No IPortletType exists for ID " + form.getTypeId());
        }

        IPortletDefinition portletDef;
        if (form.getId() == null) {
            portletDef = new PortletDefinitionImpl(portletType, form.getFname(), form.getName(), form.getTitle(),
                    form.getApplicationId(), form.getPortletName(), form.isFramework());
        } else {
            portletDef = portletDefinitionRegistry.getPortletDefinition(form.getId());
            portletDef.setType(portletType);
            portletDef.setFName(form.getFname());
            portletDef.setName(form.getName());
            portletDef.setTitle(form.getTitle());
            portletDef.getPortletDescriptorKey().setWebAppName(form.getApplicationId());
            portletDef.getPortletDescriptorKey().setPortletName(form.getPortletName());
            portletDef.getPortletDescriptorKey().setFrameworkPortlet(form.isFramework());
        }
        portletDef.setDescription(form.getDescription());
        portletDef.setTimeout(form.getTimeout());

        // Make parameters (NB:  these are different from preferences) in the
        // portletDef reflect the state of the form, in case any have changed.
        for (String key : form.getParameters().keySet()) {
            String value = form.getParameters().get(key).getValue();
            if (!StringUtils.isBlank(value)) {
                portletDef.addParameter(key, value);
            }
        }
        portletDef.addParameter(IPortletDefinition.EDITABLE_PARAM, Boolean.toString(form.isEditable()));
        portletDef.addParameter(IPortletDefinition.CONFIGURABLE_PARAM, Boolean.toString(form.isConfigurable()));
        portletDef.addParameter(IPortletDefinition.HAS_HELP_PARAM, Boolean.toString(form.isHasHelp()));
        portletDef.addParameter(IPortletDefinition.HAS_ABOUT_PARAM, Boolean.toString(form.isHasAbout()));

        // Now add portlet preferences
        List<IPortletPreference> preferenceList = new ArrayList<>();
        for (String key : form.getPortletPreferences().keySet()) {
            List<String> prefValues = form.getPortletPreferences().get(key).getValue();
            if (prefValues != null && prefValues.size() > 0) {
                String[] values = prefValues.toArray(new String[prefValues.size()]);
                BooleanAttribute readOnly = form.getPortletPreferenceReadOnly().get(key);
                preferenceList.add(new PortletPreferenceImpl(key, readOnly.getValue(), values));
            }
        }
        portletDef.setPortletPreferences(preferenceList);

        // Lastly update the PortletDefinition's lifecycle state & lifecycle-related metadata
        updateLifecycleState(form, portletDef, publisher);

        // The final parameter of IGroupMembers is used to set the initial SUBSCRIBE permission set
        portletPublishingService.savePortletDefinition(portletDef, publisher, categories,
                new ArrayList<>(subscribePrincipalSet));
        //updatePermissions(portletDef, subscribePrincipalSet, IPermission.PORTLET_SUBSCRIBER_ACTIVITY);
        updatePermissions(portletDef, browsePrincipalSet, IPermission.PORTLET_BROWSE_ACTIVITY);

        return this.createPortletDefinitionForm(publisher, portletDef.getPortletDefinitionId().getStringId());
    }

    /*
     * Update permissions for activity for portlet definition. Adds new principals' permissions passed in and removes
     * principals' permissions if not in the list for the given activity.
     */
    private void updatePermissions(IPortletDefinition def, Set<IGroupMember> newPrincipals, String activity) {
        final String portletTargetId = PermissionHelper.permissionTargetIdForPortletDefinition(def);

        /* We are concerned with PORTAL_SUBSCRIBE system */
        final IUpdatingPermissionManager pm = authorizationService
                .newUpdatingPermissionManager(IPermission.PORTAL_SUBSCRIBE);

        /* Create the new permissions array */
        final List<IPermission> newPermissions = new ArrayList<>();
        for (final IGroupMember newPrincipal : newPrincipals) {
            final IAuthorizationPrincipal authorizationPrincipal = authorizationService.newPrincipal(newPrincipal);
            final IPermission permission = pm.newPermission(authorizationPrincipal);
            permission.setType(IPermission.PERMISSION_TYPE_GRANT);
            permission.setActivity(activity);
            permission.setTarget(portletTargetId);
            newPermissions.add(permission);
        }

        /* Remove former permissions for this portlet / activity */
        final IPermission[] oldPermissions = pm.getPermissions(activity, portletTargetId);
        pm.removePermissions(oldPermissions);

        /* Add the new permissions */
        pm.addPermissions(newPermissions.toArray(new IPermission[newPermissions.size()]));
    }

    /**
     * Delete the portlet with the given portlet ID.
     *
     * @param person the person removing the portlet
     * @param form
     */
    public void removePortletRegistration(IPerson person, PortletDefinitionForm form) {

        /* TODO:  Service-Layer Security Reboot (great need of refactoring with a community-approved plan in place) */
        // Arguably a check here is redundant since -- in the current
        // portlet-manager webflow -- you can't get to this point in the
        // conversation with out first obtaining a PortletDefinitionForm;  but
        // it makes sense to check permissions here as well since the route(s)
        // to reach this method could evolve in the future.

        // Let's enforce the policy that you may only delete a portlet thet's
        // currently in a lifecycle state you have permission to MANAGE.
        // (They're hierarchical.)
        if (!hasLifecyclePermission(person, form.getLifecycleState(), form.getCategories())) {
            logger.warn("User '" + person.getUserName() + "' attempted to remove portlet '" + form.getFname()
                    + "' without the proper MANAGE permission");
            throw new SecurityException("Not Authorized");
        }

        IPortletDefinition def = portletDefinitionRegistry.getPortletDefinition(form.getId());
        /*
         * It's very important to remove portlets via the portletPublishingService
         * because that API cleans up details like category memberships and permissions.
         */
        portletPublishingService.removePortletDefinition(def, person);

    }

    /**
     * Check if the link to the Fragment admin portlet should display in the status message.
     *
     * Checks that the portlet is new, that the portlet has been published and that
     * the user has necessary permissions to go to the fragment admin page.
     *
     * @param person the person publishing/editing the portlet
     * @param form the portlet being editted
     * @param portletId the id of the saved portlet
     * @return true If all three conditions are met
     */
    public boolean shouldDisplayLayoutLink(IPerson person, PortletDefinitionForm form, String portletId) {
        if (!form.isNew()) {
            return false;
        }

        // only include the "do layout" link for published portlets.
        if (form.getLifecycleState() != PortletLifecycleState.PUBLISHED) {
            return false;
        }

        // check that the user can edit at least 1 fragment.
        Map<String, String> layouts = fragmentAdminHelper.getAuthorizedDlmFragments(person.getUserName());
        if (layouts == null || layouts.isEmpty()) {
            return false;
        }

        // check that the user has subscribe priv.
        IAuthorizationPrincipal authPrincipal = authorizationService.newPrincipal(person.getUserName(),
                EntityEnum.PERSON.getClazz());
        if (!authPrincipal.canSubscribe(portletId)) {
            return false;
        }

        return true;
    }

    /**
     * Get the link to the fragment admin portlet.
     *
     * @param request the current http request.
     * @return the portlet link
     */
    public String getFragmentAdminURL(HttpServletRequest request) {
        IPortalUrlBuilder builder = urlProvider.getPortalUrlBuilderByPortletFName(request,
                PORTLET_FNAME_FRAGMENT_ADMIN_PORTLET, UrlType.RENDER);
        IPortletUrlBuilder portletUrlBuilder = builder.getTargetedPortletUrlBuilder();
        portletUrlBuilder.setPortletMode(PortletMode.VIEW);
        portletUrlBuilder.setWindowState(WindowState.MAXIMIZED);

        return builder.getUrlString();
    }

    /**
     * Get a list of the key names of the currently-set arbitrary portlet
     * preferences.
     *
     * @param form
     * @return
     */
    public Set<String> getArbitraryPortletPreferenceNames(PortletDefinitionForm form) {
        // set default values for all portlet parameters
        PortletPublishingDefinition cpd = this.portletPublishingDefinitionDao
                .getChannelPublishingDefinition(form.getTypeId());
        Set<String> currentPrefs = new HashSet<String>();
        currentPrefs.addAll(form.getPortletPreferences().keySet());
        for (Step step : cpd.getSteps()) {
            if (step.getPreferences() != null) {
                for (Preference pref : step.getPreferences()) {
                    currentPrefs.remove(pref.getName());
                }
            }
        }
        return currentPrefs;
    }

    /**
     * If the portlet is a portlet and if one of the supported portlet modes is {@link IPortletRenderer#CONFIG}
     */
    public boolean supportsConfigMode(PortletDefinitionForm form) {
        final Tuple<String, String> portletDescriptorKeys = this.getPortletDescriptorKeys(form);
        if (portletDescriptorKeys == null) {
            return false;
        }
        final String portletAppId = portletDescriptorKeys.first;
        final String portletName = portletDescriptorKeys.second;

        final PortletRegistryService portletRegistryService = this.portalDriverContainerServices
                .getPortletRegistryService();
        final PortletDefinition portletDescriptor;
        try {
            portletDescriptor = portletRegistryService.getPortlet(portletAppId, portletName);
        } catch (PortletContainerException e) {
            this.logger.warn("Failed to load portlet descriptor for appId='" + portletAppId + "', portletName='"
                    + portletName + "'", e);
            return false;
        }

        if (portletDescriptor == null) {
            return false;
        }

        //Iterate over supported portlet modes, this ignores the content types for now
        final List<? extends Supports> supports = portletDescriptor.getSupports();
        for (final Supports support : supports) {
            final List<String> portletModes = support.getPortletModes();
            for (final String portletMode : portletModes) {
                if (IPortletRenderer.CONFIG.equals(PortletUtils.getPortletMode(portletMode))) {
                    return true;
                }
            }
        }

        return false;
    }

    private static final Pattern PARAM_PATTERN = Pattern.compile("^([^\\[]+)\\['([^\\']+)'\\]\\.value$");

    public void cleanOptions(PortletDefinitionForm form, PortletRequest request) {
        // Add permission parameters to permissions collection
        form.clearPermissions();
        for (String activity : PORTLET_SUBSCRIBE_ACTIVITIES) {
            addPermissionsFromRequestToForm(form, request, activity);
        }

        //Names of valid preferences and parameters
        final Set<String> preferenceNames = new HashSet<String>();
        final Set<String> parameterNames = new HashSet<String>();

        //Read all of the submitted channel parameter and portlet preference names from the request
        for (final Enumeration<String> e = request.getParameterNames(); e.hasMoreElements();) {
            final String name = e.nextElement();
            final Matcher nameMatcher = PARAM_PATTERN.matcher(name);
            if (nameMatcher.matches()) {
                final String paramType = nameMatcher.group(1);
                final String paramName = nameMatcher.group(2);

                if ("portletPreferences".equals(paramType)) {
                    preferenceNames.add(paramName);
                } else if ("parameters".equals(paramType)) {
                    parameterNames.add(paramName);
                }
            }
        }

        //Add all of the parameter and preference names that have default values in the CPD into the valid name sets
        final PortletPublishingDefinition cpd = this.portletPublishingDefinitionDao
                .getChannelPublishingDefinition(form.getTypeId());
        for (final Step step : cpd.getSteps()) {
            final List<Parameter> parameters = step.getParameters();
            if (parameters != null) {
                for (final Parameter parameter : parameters) {
                    final JAXBElement<? extends ParameterInputType> parameterInput = parameter.getParameterInput();
                    if (parameterInput != null) {
                        final ParameterInputType parameterInputType = parameterInput.getValue();
                        if (parameterInputType != null && parameterInputType.getDefault() != null) {
                            parameterNames.add(parameter.getName());
                        }
                    }
                }
            }

            final List<Preference> preferences = step.getPreferences();
            if (preferences != null) {
                for (final Preference preference : preferences) {
                    final JAXBElement<? extends PreferenceInputType> preferenceInput = preference
                            .getPreferenceInput();
                    final PreferenceInputType preferenceInputType = preferenceInput.getValue();
                    if (preferenceInputType instanceof MultiValuedPreferenceInputType) {
                        final MultiValuedPreferenceInputType multiValuedPreferenceInputType = (MultiValuedPreferenceInputType) preferenceInputType;
                        final List<String> defaultValues = multiValuedPreferenceInputType.getDefaults();
                        if (defaultValues != null && !defaultValues.isEmpty()) {
                            preferenceNames.add(preference.getName());
                        }
                    } else if (preferenceInputType instanceof SingleValuedPreferenceInputType) {
                        final SingleValuedPreferenceInputType SingleValuedPreferenceInputType = (SingleValuedPreferenceInputType) preferenceInputType;
                        if (SingleValuedPreferenceInputType.getDefault() != null) {
                            preferenceNames.add(preference.getName());
                        }
                    }
                }
            }
        }

        //Remove portlet preferences from the form object that were not part of this request or defined in the CPD
        // - do it only if portlet doesn't support configMode
        if (!this.supportsConfigMode(form)) {
            final Map<String, StringListAttribute> portletPreferences = form.getPortletPreferences();
            final Map<String, BooleanAttribute> portletPreferencesOverrides = form.getPortletPreferenceReadOnly();

            for (final Iterator<Entry<String, StringListAttribute>> portletPreferenceEntryItr = portletPreferences
                    .entrySet().iterator(); portletPreferenceEntryItr.hasNext();) {
                final Map.Entry<String, StringListAttribute> portletPreferenceEntry = portletPreferenceEntryItr
                        .next();
                final String key = portletPreferenceEntry.getKey();
                final StringListAttribute valueAttr = portletPreferenceEntry.getValue();
                if (!preferenceNames.contains(key) || valueAttr == null) {
                    portletPreferenceEntryItr.remove();
                    portletPreferencesOverrides.remove(key);
                } else {
                    final List<String> values = valueAttr.getValue();
                    for (final Iterator<String> iter = values.iterator(); iter.hasNext();) {
                        String value = iter.next();
                        if (value == null) {
                            iter.remove();
                        }
                    }
                    if (values.size() == 0) {
                        portletPreferenceEntryItr.remove();
                        portletPreferencesOverrides.remove(key);
                    }
                }
            }
        }

        final Map<String, Attribute> parameters = form.getParameters();

        for (final Iterator<Entry<String, Attribute>> parameterEntryItr = parameters.entrySet()
                .iterator(); parameterEntryItr.hasNext();) {
            final Entry<String, Attribute> parameterEntry = parameterEntryItr.next();
            final String key = parameterEntry.getKey();
            final Attribute value = parameterEntry.getValue();

            if (!parameterNames.contains(key) || value == null || StringUtils.isBlank(value.getValue())) {
                parameterEntryItr.remove();
            }
        }
    }

    private void addPermissionsFromRequestToForm(PortletDefinitionForm form, PortletRequest request,
            String activity) {
        final String ending = "_" + activity;
        for (final String name : request.getParameterMap().keySet()) {
            if (name.endsWith(ending)) {
                form.addPermission(name);
            }
        }
    }

    /**
     * Retreive the list of portlet application contexts currently available in
     * this portlet container.
     *
     * @return list of portlet context
     */
    public List<PortletApplicationDefinition> getPortletApplications() {
        final PortletRegistryService portletRegistryService = portalDriverContainerServices
                .getPortletRegistryService();
        final List<PortletApplicationDefinition> contexts = new ArrayList<PortletApplicationDefinition>();

        for (final Iterator<String> iter = portletRegistryService.getRegisteredPortletApplicationNames(); iter
                .hasNext();) {
            final String applicationName = iter.next();
            final PortletApplicationDefinition applicationDefninition;
            try {
                applicationDefninition = portletRegistryService.getPortletApplication(applicationName);
            } catch (PortletContainerException e) {
                throw new RuntimeException(
                        "Failed to load PortletApplicationDefinition for '" + applicationName + "'");
            }

            final List<? extends PortletDefinition> portlets = applicationDefninition.getPortlets();
            Collections.sort(portlets,
                    new ComparableExtractingComparator<PortletDefinition, String>(String.CASE_INSENSITIVE_ORDER) {
                        @Override
                        protected String getComparable(PortletDefinition o) {
                            final List<? extends DisplayName> displayNames = o.getDisplayNames();
                            if (displayNames != null && displayNames.size() > 0) {
                                return displayNames.get(0).getDisplayName();
                            }

                            return o.getPortletName();
                        }
                    });

            contexts.add(applicationDefninition);
        }

        Collections.sort(contexts, new ComparableExtractingComparator<PortletApplicationDefinition, String>(
                String.CASE_INSENSITIVE_ORDER) {
            @Override
            protected String getComparable(PortletApplicationDefinition o) {
                final String portletContextName = o.getName();
                if (portletContextName != null) {
                    return portletContextName;
                }

                final String applicationName = o.getContextPath();
                if ("/".equals(applicationName)) {
                    return "ROOT";
                }

                if (applicationName.startsWith("/")) {
                    return applicationName.substring(1);
                }

                return applicationName;
            }
        });
        return contexts;
    }

    /**
     * Get a portlet descriptor matching the current portlet definition form.
     * If the current form does not represent a portlet, the application or
     * portlet name fields are blank, or the portlet description cannot be
     * retrieved, the method will return <code>null</code>.
     *
     * @param form
     * @return
     */
    public PortletDefinition getPortletDescriptor(PortletDefinitionForm form) {
        final Tuple<String, String> portletDescriptorKeys = this.getPortletDescriptorKeys(form);
        if (portletDescriptorKeys == null) {
            return null;
        }
        final String portletAppId = portletDescriptorKeys.first;
        final String portletName = portletDescriptorKeys.second;

        final PortletRegistryService portletRegistryService = portalDriverContainerServices
                .getPortletRegistryService();
        try {
            PortletDefinition portletDD = portletRegistryService.getPortlet(portletAppId, portletName);
            return portletDD;
        } catch (PortletContainerException e) {
            e.printStackTrace();
            return null;
        }
    }

    /**
     * Pre-populate a new {@link PortletDefinitionForm} with information from
     * the {@link PortletDefinition}.
     *
     * @param form
     */
    public void loadDefaultsFromPortletDefinitionIfNew(PortletDefinitionForm form) {

        if (!form.isNew()) {
            // Get out;  we only prepopulate new portlets
            return;
        }

        // appName/portletName must be set at this point
        Validate.notBlank(form.getApplicationId(), "ApplicationId not set");
        Validate.notBlank(form.getPortletName(), "PortletName not set");

        final PortletRegistryService portletRegistryService = portalDriverContainerServices
                .getPortletRegistryService();
        final PortletDefinition portletDef;
        try {
            portletDef = portletRegistryService.getPortlet(form.getApplicationId(), form.getPortletName());
        } catch (PortletContainerException e) {
            this.logger.warn("Failed to load portlet descriptor for appId='" + form.getApplicationId()
                    + "', portletName='" + form.getPortletName() + "'", e);
            return;
        }

        form.setTitle(portletDef.getPortletName());
        form.setName(portletDef.getPortletName());
        for (Supports supports : portletDef.getSupports()) {
            for (String mode : supports.getPortletModes()) {
                if ("edit".equalsIgnoreCase(mode)) {
                    form.setEditable(true);
                } else if ("help".equalsIgnoreCase(mode)) {
                    form.setHasHelp(true);
                } else if ("config".equalsIgnoreCase(mode)) {
                    form.setConfigurable(true);
                }
            }
        }
    }

    public PortletLifecycleState[] getLifecycleStates() {
        return PortletLifecycleState.values();
    }

    public Set<PortletLifecycleState> getAllowedLifecycleStates(IPerson person,
            SortedSet<JsonEntityBean> categories) {
        Set<PortletLifecycleState> states = new TreeSet<PortletLifecycleState>();
        if (hasLifecyclePermission(person, PortletLifecycleState.MAINTENANCE, categories)) {
            states.add(PortletLifecycleState.CREATED);
            states.add(PortletLifecycleState.APPROVED);
            states.add(PortletLifecycleState.EXPIRED);
            states.add(PortletLifecycleState.PUBLISHED);
            states.add(PortletLifecycleState.MAINTENANCE);
        } else if (hasLifecyclePermission(person, PortletLifecycleState.EXPIRED, categories)) {
            states.add(PortletLifecycleState.CREATED);
            states.add(PortletLifecycleState.APPROVED);
            states.add(PortletLifecycleState.EXPIRED);
            states.add(PortletLifecycleState.PUBLISHED);
        } else if (hasLifecyclePermission(person, PortletLifecycleState.PUBLISHED, categories)) {
            states.add(PortletLifecycleState.CREATED);
            states.add(PortletLifecycleState.APPROVED);
            states.add(PortletLifecycleState.PUBLISHED);
        } else if (hasLifecyclePermission(person, PortletLifecycleState.APPROVED, categories)) {
            states.add(PortletLifecycleState.CREATED);
            states.add(PortletLifecycleState.APPROVED);
        } else if (hasLifecyclePermission(person, PortletLifecycleState.CREATED, categories)) {
            states.add(PortletLifecycleState.CREATED);
        }
        return states;
    }

    public boolean hasLifecyclePermission(IPerson person, PortletLifecycleState state,
            SortedSet<JsonEntityBean> categories) {
        EntityIdentifier ei = person.getEntityIdentifier();
        IAuthorizationPrincipal ap = authorizationService.newPrincipal(ei.getKey(), ei.getType());

        final String activity;
        switch (state) {
        case APPROVED: {
            activity = IPermission.PORTLET_MANAGER_APPROVED_ACTIVITY;
            break;
        }
        case CREATED: {
            activity = IPermission.PORTLET_MANAGER_CREATED_ACTIVITY;
            break;
        }
        case PUBLISHED: {
            activity = IPermission.PORTLET_MANAGER_ACTIVITY;
            break;
        }
        case EXPIRED: {
            activity = IPermission.PORTLET_MANAGER_EXPIRED_ACTIVITY;
            break;
        }
        case MAINTENANCE: {
            activity = IPermission.PORTLET_MANAGER_MAINTENANCE_ACTIVITY;
            break;
        }
        default: {
            throw new IllegalArgumentException("");
        }
        }
        if (ap.hasPermission(IPermission.PORTAL_PUBLISH, activity, IPermission.ALL_PORTLETS_TARGET)) {
            logger.debug("Found permission for category ALL_PORTLETS and lifecycle state " + state.toString());
            return true;
        }

        for (JsonEntityBean category : categories) {
            if (ap.canManage(state, category.getId())) {
                logger.debug("Found permission for category " + category.getName() + " and lifecycle state "
                        + state.toString());
                return true;
            }
        }
        logger.debug("No permission for lifecycle state " + state.toString());
        return false;
    }

    public IPortletWindowId getDelegateWindowId(ExternalContext externalContext, String fname) {
        final PortletRequest nativeRequest = (PortletRequest) externalContext.getNativeRequest();
        final PortletSession portletSession = nativeRequest.getPortletSession();
        return (IPortletWindowId) portletSession.getAttribute(RenderPortletTag.DEFAULT_SESSION_KEY_PREFIX + fname);
    }

    public boolean configModeAction(ExternalContext externalContext, String fname) throws IOException {

        final ActionRequest actionRequest = (ActionRequest) externalContext.getNativeRequest();
        final ActionResponse actionResponse = (ActionResponse) externalContext.getNativeResponse();

        final IPortletWindowId portletWindowId = this.getDelegateWindowId(externalContext, fname);
        if (portletWindowId == null) {
            throw new IllegalStateException(
                    "Cannot execute configModeAciton without a delegate window ID in the session for key: "
                            + RenderPortletTag.DEFAULT_SESSION_KEY_PREFIX + fname);
        }

        final PortletDelegationDispatcher requestDispatcher = this.portletDelegationLocator
                .getRequestDispatcher(actionRequest, portletWindowId);

        final DelegationActionResponse delegationResponse = requestDispatcher.doAction(actionRequest,
                actionResponse);

        final String redirectLocation = delegationResponse.getRedirectLocation();
        final DelegateState delegateState = delegationResponse.getDelegateState();
        if (redirectLocation != null
                || (delegationResponse.getPortletMode() != null
                        && !IPortletRenderer.CONFIG.equals(delegationResponse.getPortletMode()))
                || !IPortletRenderer.CONFIG.equals(delegateState.getPortletMode())) {

            //The portlet sent a redirect OR changed it's mode away from CONFIG, assume it is done
            return true;
        }

        return false;
    }

    /**
     * updates the editPortlet form with the portletType
     * of the first (and only) portletDefinition passed in through the Map of
     * portlet definitions.
     * @param portletDefinitions
     * @param form
     * @return PortletPublishingDefinition of the first portlet definition in the
     * list, null if the list is empty or has more than one element.
     */
    public PortletPublishingDefinition updateFormForSinglePortletType(
            Map<IPortletType, PortletPublishingDefinition> portletDefinitions, PortletDefinitionForm form) {
        if (portletDefinitions.size() != 1) {
            return null;
        }
        IPortletType portletType = portletDefinitions.keySet().iterator().next();
        form.setTypeId(portletType.getId());
        PortletPublishingDefinition cpd = portletPublishingDefinitionDao
                .getChannelPublishingDefinition(portletType.getId());
        form.setChannelPublishingDefinition(cpd);

        return cpd;
    }

    public boolean offerPortletSelection(PortletDefinitionForm form) {
        final IPortletType portletType = this.portletTypeRegistry.getPortletType(form.getTypeId());
        final PortletPublishingDefinition portletPublishingDefinition = this.portletPublishingDefinitionDao
                .getChannelPublishingDefinition(portletType.getId());
        final PortletDescriptor portletDescriptor = portletPublishingDefinition.getPortletDescriptor();
        if (portletDescriptor == null) {
            return true;
        }

        final Boolean isFramework = portletDescriptor.isIsFramework();
        if (isFramework != null && isFramework) {
            form.setFramework(isFramework);
        } else {
            final String webAppName = portletDescriptor.getWebAppName();
            form.setApplicationId(webAppName);
        }

        final String portletName = portletDescriptor.getPortletName();
        form.setPortletName(portletName);

        return false;
    }

    public Map<IPortletType, PortletPublishingDefinition> getAllowableChannelPublishingDefinitions(IPerson user) {

        Map<IPortletType, PortletPublishingDefinition> rslt;

        final Map<IPortletType, PortletPublishingDefinition> rawMap = portletPublishingDefinitionDao
                .getChannelPublishingDefinitions();
        final IAuthorizationPrincipal principal = AuthorizationPrincipalHelper.principalFromUser(user);
        if (principal.hasPermission(IPermission.PORTAL_PUBLISH, IPermission.PORTLET_MANAGER_SELECT_PORTLET_TYPE,
                IPermission.ALL_PORTLET_TYPES)) {
            // Send the whole collection back...
            rslt = rawMap;
        } else {
            // Filter the collection by permissions...
            rslt = new HashMap<IPortletType, PortletPublishingDefinition>();
            for (Map.Entry<IPortletType, PortletPublishingDefinition> y : rawMap.entrySet()) {
                if (principal.hasPermission(IPermission.PORTAL_PUBLISH,
                        IPermission.PORTLET_MANAGER_SELECT_PORTLET_TYPE, y.getKey().getName())) {
                    rslt.put(y.getKey(), y.getValue());
                }
            }
        }

        return rslt;

    }

    protected Tuple<String, String> getPortletDescriptorKeys(PortletDefinitionForm form) {
        if (form.getPortletName() == null || (form.getApplicationId() == null && !form.isFramework())) {
            return null;
        }

        final String portletAppId;
        if (form.isFramework()) {
            portletAppId = this.servletContext.getContextPath();
        } else {
            portletAppId = form.getApplicationId();
        }

        final String portletName = form.getPortletName();

        return new Tuple<String, String>(portletAppId, portletName);
    }

    private void updateLifecycleState(PortletDefinitionForm form, IPortletDefinition portletDef,
            IPerson publisher) {

        /*
         * Manage the metadata for each possible lifecycle state in turn...
         */

        Date now = new Date(); // Will be entered as the timestamp for states that we trigger
        PortletLifecycleState selectedLifecycleState = form.getLifecycleState();

        /*
         * APPROVED
         */
        if (selectedLifecycleState.isEqualToOrAfter(PortletLifecycleState.APPROVED)) {
            // We are the 'approver' if it isn't previously approved...
            if (portletDef.getApprovalDate() == null) {
                portletDef.setApproverId(publisher.getID());
                portletDef.setApprovalDate(now);
            }
            if (selectedLifecycleState.equals(PortletLifecycleState.APPROVED) && form.getPublishDate() != null
            // Permissions check required (of course) to use the auto-publish feature
                    && hasLifecyclePermission(publisher, PortletLifecycleState.PUBLISHED, form.getCategories())) {
                // We are also the 'publisher' if we scheduled the portlet for (future) publication...
                portletDef.setPublishDate(form.getPublishDateTime());
                portletDef.setPublisherId(publisher.getID());
            }
        } else {
            // Clear previous approval fields, if present...
            portletDef.setApprovalDate(null);
            portletDef.setApproverId(-1);
        }

        /*
         * PUBLISHED
         */
        if (selectedLifecycleState.isEqualToOrAfter(PortletLifecycleState.PUBLISHED)) {
            // We are the 'publisher' if it isn't previously published or the publish time hasn't hit yet...
            if (portletDef.getPublishDate() == null || portletDef.getPublishDate().after(now)) {
                portletDef.setPublisherId(publisher.getID());
                portletDef.setPublishDate(now);
            }
            if (selectedLifecycleState.equals(PortletLifecycleState.PUBLISHED) && form.getExpirationDate() != null
            // Permissions check required (of course) to use the auto-expire feature
                    && hasLifecyclePermission(publisher, PortletLifecycleState.EXPIRED, form.getCategories())) {
                // We are also the 'expirer' if we scheduled the portlet for (future) expiration...
                portletDef.setExpirationDate(form.getExpirationDateTime());
                portletDef.setExpirerId(publisher.getID());
            }
        } else if (!selectedLifecycleState.equals(PortletLifecycleState.APPROVED)
                || form.getPublishDate() == null) {
            // Clear previous publishing fields, if present...
            portletDef.setPublishDate(null);
            portletDef.setPublisherId(-1);
        }

        /*
         * EXPIRED
         */
        if (selectedLifecycleState.equals(PortletLifecycleState.EXPIRED)) {
            // We are only the 'expirer' if we specifically choose EXPIRED
            // (MAINTENANCE mode is not considered expired)
            portletDef.setExpirerId(publisher.getID());
            portletDef.setExpirationDate(now);
        } else if (!selectedLifecycleState.equals(PortletLifecycleState.PUBLISHED)
                || form.getExpirationDate() == null) {
            // Clear previous expiration fields, if present...
            portletDef.setExpirationDate(null);
            portletDef.setExpirerId(-1);
        }

        /*
         * MAINTENANCE
         */
        if (selectedLifecycleState.equals(PortletLifecycleState.MAINTENANCE)) {
            // We are placing the portlet into MAINTENANCE mode;
            // an admin will restore it (manually) when available
            portletDef.addParameter(PortletLifecycleState.MAINTENANCE_MODE_PARAMETER_NAME, "true");
        } else {
            // Otherwise we must remove the MAINTENANCE flag, if present
            portletDef.removeParameter(PortletLifecycleState.MAINTENANCE_MODE_PARAMETER_NAME);
        }
    }
}