org.jasig.portal.portlet.rendering.PortletEventCoordinatationService.java Source code

Java tutorial

Introduction

Here is the source code for org.jasig.portal.portlet.rendering.PortletEventCoordinatationService.java

Source

/**
 * Licensed to Jasig under one or more contributor license
 * agreements. See the NOTICE file distributed with this work
 * for additional information regarding copyright ownership.
 * Jasig 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:
 *
 * 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.jasig.portal.portlet.rendering;

import java.io.Serializable;
import java.io.StringReader;
import java.util.Collections;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Queue;
import java.util.Set;

import javax.portlet.Event;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.xml.bind.JAXBContext;
import javax.xml.bind.JAXBElement;
import javax.xml.bind.JAXBException;
import javax.xml.bind.Unmarshaller;
import javax.xml.namespace.QName;
import javax.xml.stream.XMLInputFactory;
import javax.xml.stream.XMLStreamException;
import javax.xml.stream.XMLStreamReader;

import net.sf.ehcache.Ehcache;
import net.sf.ehcache.Element;

import org.apache.commons.lang.StringUtils;
import org.apache.pluto.container.PortletContainer;
import org.apache.pluto.container.PortletContainerException;
import org.apache.pluto.container.PortletWindow;
import org.apache.pluto.container.driver.PortletContextService;
import org.apache.pluto.container.om.portlet.ContainerRuntimeOption;
import org.apache.pluto.container.om.portlet.EventDefinition;
import org.apache.pluto.container.om.portlet.EventDefinitionReference;
import org.apache.pluto.container.om.portlet.PortletApplicationDefinition;
import org.apache.pluto.container.om.portlet.PortletDefinition;
import org.jasig.portal.EntityIdentifier;
import org.jasig.portal.IUserPreferencesManager;
import org.jasig.portal.layout.IUserLayoutManager;
import org.jasig.portal.portlet.container.EventImpl;
import org.jasig.portal.portlet.om.IPortletDefinition;
import org.jasig.portal.portlet.om.IPortletDefinitionId;
import org.jasig.portal.portlet.om.IPortletEntity;
import org.jasig.portal.portlet.om.IPortletEntityId;
import org.jasig.portal.portlet.om.IPortletWindow;
import org.jasig.portal.portlet.om.IPortletWindowId;
import org.jasig.portal.portlet.registry.IPortletDefinitionRegistry;
import org.jasig.portal.portlet.registry.IPortletEntityRegistry;
import org.jasig.portal.portlet.registry.IPortletWindowRegistry;
import org.jasig.portal.security.IAuthorizationPrincipal;
import org.jasig.portal.security.IPerson;
import org.jasig.portal.services.AuthorizationService;
import org.jasig.portal.url.IPortalRequestUtils;
import org.jasig.portal.user.IUserInstance;
import org.jasig.portal.user.IUserInstanceManager;
import org.jasig.portal.utils.Tuple;
import org.jasig.portal.utils.web.PortalWebUtils;
import org.jasig.portal.xml.XmlUtilities;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.stereotype.Service;

import com.google.common.base.Function;
import com.google.common.collect.Lists;

/**
 * uPortal's approach to event coordination is to simply queue the events and rely on the {@link IPortletExecutionManager}
 * to handle event execution. What this class does is for each {@link #processEvents(PortletContainer, PortletWindow, HttpServletRequest, HttpServletResponse, List)}
 * request from the portlet container is to add them to a Queue scoped to the portal's request.
 * 
 * It also provides {@link #getPortletEventQueue(PortletEventQueue, List, HttpServletRequest)} which is used to determine
 * which events to send to which portlet windows. 
 * 
 * @author Eric Dalquist
 * @version $Revision$
 */
@Service("eventCoordinationService")
public class PortletEventCoordinatationService implements IPortletEventCoordinationService {
    /**
    * 
    */
    public static final String GLOBAL_EVENT__CONTAINER_OPTION = "org.jasig.portal.globalEvent";

    private static final String PORTLET_EVENT_QUEUE = PortletEventCoordinatationService.class.getName()
            + ".PORTLET_EVENT_QUEUE";

    protected final Logger logger = LoggerFactory.getLogger(this.getClass());

    private IPortletWindowRegistry portletWindowRegistry;
    private IPortletEntityRegistry portletEntityRegistry;
    private IPortletDefinitionRegistry portletDefinitionRegistry;
    private IUserInstanceManager userInstanceManager;
    private Ehcache supportedEventCache;
    private IPortalRequestUtils portalRequestUtils;
    private XmlUtilities xmlUtilities;
    private PortletContextService portletContextService;

    @Autowired
    public void setPortletContextService(PortletContextService portletContextService) {
        this.portletContextService = portletContextService;
    }

    @Autowired
    public void setXmlUtilities(XmlUtilities xmlUtilities) {
        this.xmlUtilities = xmlUtilities;
    }

    @Autowired
    public void setPortalRequestUtils(IPortalRequestUtils portalRequestUtils) {
        this.portalRequestUtils = portalRequestUtils;
    }

    @Autowired
    public void setSupportedEventCache(
            @Qualifier("org.jasig.portal.portlet.rendering.SupportedEventCache") Ehcache supportedEventCache) {
        this.supportedEventCache = supportedEventCache;
    }

    @Autowired
    public void setPortletDefinitionRegistry(IPortletDefinitionRegistry portletDefinitionRegistry) {
        this.portletDefinitionRegistry = portletDefinitionRegistry;
    }

    @Autowired
    public void setUserInstanceManager(IUserInstanceManager userInstanceManager) {
        this.userInstanceManager = userInstanceManager;
    }

    @Autowired
    public void setPortletWindowRegistry(IPortletWindowRegistry portletWindowRegistry) {
        this.portletWindowRegistry = portletWindowRegistry;
    }

    @Autowired
    public void setPortletEntityRegistry(IPortletEntityRegistry portletEntityRegistry) {
        this.portletEntityRegistry = portletEntityRegistry;
    }

    /**
     * Returns a request scoped PortletEventQueue used to track events to process and events to dispatch
     */
    @Override
    public PortletEventQueue getPortletEventQueue(HttpServletRequest request) {
        request = this.portalRequestUtils.getOriginalPortalRequest(request);

        synchronized (PortalWebUtils.getRequestAttributeMutex(request)) {
            PortletEventQueue portletEventQueue = (PortletEventQueue) request.getAttribute(PORTLET_EVENT_QUEUE);
            if (portletEventQueue == null) {
                portletEventQueue = new PortletEventQueue();
                request.setAttribute(PORTLET_EVENT_QUEUE, portletEventQueue);
            }
            return portletEventQueue;
        }
    }

    @Override
    public void processEvents(PortletContainer container, PortletWindow plutoPortletWindow,
            HttpServletRequest request, HttpServletResponse response, List<Event> events) {
        final PortletEventQueue requestPortletEventQueue = this.getPortletEventQueue(request);
        this.logger.debug("Queued {} from {}", events, plutoPortletWindow);

        final IPortletWindow portletWindow = this.portletWindowRegistry.convertPortletWindow(request,
                plutoPortletWindow);
        final IPortletWindowId portletWindowId = portletWindow.getPortletWindowId();

        //Add list transformer to convert Event to QueuedEvent
        final List<QueuedEvent> queuedEvents = Lists.transform(events, new Function<Event, QueuedEvent>() {
            /* (non-Javadoc)
             * @see com.google.common.base.Function#apply(java.lang.Object)
             */
            @Override
            public QueuedEvent apply(Event event) {
                return new QueuedEvent(portletWindowId, event);
            }
        });

        requestPortletEventQueue.addEvents(queuedEvents);
    }

    @Override
    public void resolvePortletEvents(HttpServletRequest request, PortletEventQueue portletEventQueue) {
        final Queue<QueuedEvent> events = portletEventQueue.getUnresolvedEvents();

        //Skip all processing if there are no new events.
        if (events.isEmpty()) {
            return;
        }

        //Get all the portlets the user is subscribed to
        final IUserInstance userInstance = this.userInstanceManager.getUserInstance(request);
        final IUserPreferencesManager preferencesManager = userInstance.getPreferencesManager();
        final IUserLayoutManager userLayoutManager = preferencesManager.getUserLayoutManager();

        //Make a local copy so we can remove data from it
        final Set<String> allLayoutNodeIds = new LinkedHashSet<String>(
                userLayoutManager.getAllSubscribedChannels());

        final Map<String, IPortletEntity> portletEntityCache = new LinkedHashMap<String, IPortletEntity>();

        while (!events.isEmpty()) {
            final QueuedEvent queuedEvent = events.poll();
            if (queuedEvent == null) {
                //no more queued events, done resolving
                return;
            }

            final IPortletWindowId sourceWindowId = queuedEvent.getPortletWindowId();
            final Event event = queuedEvent.getEvent();

            final boolean globalEvent = isGlobalEvent(request, sourceWindowId, event);

            final Set<IPortletDefinition> portletDefinitions = new LinkedHashSet<IPortletDefinition>();
            if (globalEvent) {
                portletDefinitions.addAll(this.portletDefinitionRegistry.getAllPortletDefinitions());
            }

            //Check each subscription to see what events it is registered to see
            for (final Iterator<String> layoutNodeIdItr = allLayoutNodeIds.iterator(); layoutNodeIdItr.hasNext();) {
                final String layoutNodeId = layoutNodeIdItr.next();

                IPortletEntity portletEntity = portletEntityCache.get(layoutNodeId);
                if (portletEntity == null) {
                    portletEntity = this.portletEntityRegistry.getOrCreatePortletEntity(request, userInstance,
                            layoutNodeId);

                    // if portlet entity registry returned null, then portlet has been deleted - remove it (see UP-3378)
                    if (portletEntity == null) {
                        layoutNodeIdItr.remove();
                        continue;
                    }

                    final IPortletDefinitionId portletDefinitionId = portletEntity.getPortletDefinitionId();
                    final PortletDefinition portletDescriptor = this.portletDefinitionRegistry
                            .getParentPortletDescriptor(portletDefinitionId);
                    if (portletDescriptor == null) {
                        //Missconfigured portlet, remove it from the list so we don't check again and ignore it
                        layoutNodeIdItr.remove();
                        continue;
                    }

                    final List<? extends EventDefinitionReference> supportedProcessingEvents = portletDescriptor
                            .getSupportedProcessingEvents();
                    //Skip portlets that don't handle any events and remove them from the set so they are not checked again
                    if (supportedProcessingEvents == null || supportedProcessingEvents.size() == 0) {
                        layoutNodeIdItr.remove();
                        continue;
                    }

                    portletEntityCache.put(layoutNodeId, portletEntity);
                }

                final IPortletDefinition portletDefinition = portletEntity.getPortletDefinition();
                final IPortletDefinitionId portletDefinitionId = portletDefinition.getPortletDefinitionId();
                if (this.supportsEvent(event, portletDefinitionId)) {
                    this.logger.debug("{} supports event {}", portletDefinition, event);

                    //If this is the default portlet entity remove the definition from the all defs set to avoid duplicate processing
                    final IPortletEntity defaultPortletEntity = this.portletEntityRegistry
                            .getOrCreateDefaultPortletEntity(request, portletDefinitionId);
                    if (defaultPortletEntity.equals(portletEntity)) {
                        portletDefinitions.remove(portletDefinition);
                    }

                    final IPortletEntityId portletEntityId = portletEntity.getPortletEntityId();
                    final Set<IPortletWindow> portletWindows = this.portletWindowRegistry
                            .getAllPortletWindowsForEntity(request, portletEntityId);

                    for (final IPortletWindow portletWindow : portletWindows) {
                        this.logger.debug("{} resolved target {}", event, portletWindow);
                        final IPortletWindowId portletWindowId = portletWindow.getPortletWindowId();
                        final Event unmarshalledEvent = this.unmarshall(portletWindow, event);
                        portletEventQueue.offerEvent(portletWindowId,
                                new QueuedEvent(sourceWindowId, unmarshalledEvent));
                    }
                } else {
                    portletDefinitions.remove(portletDefinition);
                }
            }

            if (!portletDefinitions.isEmpty()) {
                final IPerson user = userInstance.getPerson();
                final EntityIdentifier ei = user.getEntityIdentifier();
                final IAuthorizationPrincipal ap = AuthorizationService.instance().newPrincipal(ei.getKey(),
                        ei.getType());

                //If the event is global there might still be portlet definitions that need targeting
                for (final IPortletDefinition portletDefinition : portletDefinitions) {
                    final IPortletDefinitionId portletDefinitionId = portletDefinition.getPortletDefinitionId();
                    //Check if the user can render the portlet definition before doing event tests
                    if (ap.canRender(portletDefinitionId.getStringId())) {
                        if (this.supportsEvent(event, portletDefinitionId)) {
                            this.logger.debug("{} supports event {}", portletDefinition, event);

                            final IPortletEntity portletEntity = this.portletEntityRegistry
                                    .getOrCreateDefaultPortletEntity(request, portletDefinitionId);
                            final IPortletEntityId portletEntityId = portletEntity.getPortletEntityId();
                            final Set<IPortletWindow> portletWindows = this.portletWindowRegistry
                                    .getAllPortletWindowsForEntity(request, portletEntityId);

                            for (final IPortletWindow portletWindow : portletWindows) {
                                this.logger.debug("{} resolved target {}", event, portletWindow);
                                final IPortletWindowId portletWindowId = portletWindow.getPortletWindowId();
                                final Event unmarshalledEvent = this.unmarshall(portletWindow, event);
                                portletEventQueue.offerEvent(portletWindowId,
                                        new QueuedEvent(sourceWindowId, unmarshalledEvent));
                            }
                        }
                    }
                }
            }
        }
    }

    protected boolean isGlobalEvent(HttpServletRequest request, IPortletWindowId sourceWindowId, Event event) {
        final IPortletWindow portletWindow = this.portletWindowRegistry.getPortletWindow(request, sourceWindowId);
        final IPortletEntity portletEntity = portletWindow.getPortletEntity();
        final IPortletDefinition portletDefinition = portletEntity.getPortletDefinition();
        final IPortletDefinitionId portletDefinitionId = portletDefinition.getPortletDefinitionId();
        final PortletApplicationDefinition parentPortletApplicationDescriptor = this.portletDefinitionRegistry
                .getParentPortletApplicationDescriptor(portletDefinitionId);

        final ContainerRuntimeOption globalEvents = parentPortletApplicationDescriptor
                .getContainerRuntimeOption(GLOBAL_EVENT__CONTAINER_OPTION);
        if (globalEvents != null) {
            final QName qName = event.getQName();
            final String qNameStr = qName.toString();
            for (final String globalEvent : globalEvents.getValues()) {
                if (qNameStr.equals(globalEvent)) {
                    return true;
                }
            }
        }

        return false;
    }

    protected Event unmarshall(IPortletWindow portletWindow, Event event) {
        //TODO make two types of Event impls, one for marshalled data and one for unmarshalled data
        String value = (String) event.getValue();

        final XMLInputFactory xmlInputFactory = this.xmlUtilities.getXmlInputFactory();
        final XMLStreamReader xml;
        try {
            xml = xmlInputFactory.createXMLStreamReader(new StringReader(value));
        } catch (XMLStreamException e) {
            throw new IllegalStateException("Failed to create XMLStreamReader for portlet event: " + event, e);
        }

        // now test if object is jaxb
        final EventDefinition eventDefinitionDD = getEventDefintion(portletWindow, event.getQName());

        final PortletDefinition portletDefinition = portletWindow.getPlutoPortletWindow().getPortletDefinition();
        final PortletApplicationDefinition application = portletDefinition.getApplication();
        final String portletApplicationName = application.getName();

        final ClassLoader loader;
        try {
            loader = portletContextService.getClassLoader(portletApplicationName);
        } catch (PortletContainerException e) {
            throw new IllegalStateException(
                    "Failed to get ClassLoader for portlet application: " + portletApplicationName, e);
        }

        final String eventType = eventDefinitionDD.getValueType();
        final Class<? extends Serializable> clazz;
        try {
            clazz = loader.loadClass(eventType).asSubclass(Serializable.class);
        } catch (ClassNotFoundException e) {
            throw new IllegalArgumentException("Declared event type '" + eventType
                    + "' cannot be found in portlet application: " + portletApplicationName, e);
        }

        //TODO cache JAXBContext in registered portlet application
        final JAXBElement<? extends Serializable> result;
        try {
            final JAXBContext jc = JAXBContext.newInstance(clazz);
            final Unmarshaller unmarshaller = jc.createUnmarshaller();
            result = unmarshaller.unmarshal(xml, clazz);
        } catch (JAXBException e) {
            throw new IllegalArgumentException("Cannot create JAXBContext for event type '" + eventType
                    + "' from portlet application: " + portletApplicationName, e);
        }

        return new EventImpl(event.getQName(), result.getValue());
    }

    //TODO cache this resolution
    protected EventDefinition getEventDefintion(IPortletWindow portletWindow, QName name) {
        PortletApplicationDefinition appDD = portletWindow.getPlutoPortletWindow().getPortletDefinition()
                .getApplication();
        for (EventDefinition def : appDD.getEventDefinitions()) {
            if (def.getQName() != null) {
                if (def.getQName().equals(name))
                    return def;
            } else {
                QName tmp = new QName(appDD.getDefaultNamespace(), def.getName());
                if (tmp.equals(name))
                    return def;
            }
        }
        throw new IllegalStateException();
    }

    protected Set<QName> getAllAliases(QName eventName, PortletApplicationDefinition portletApplicationDefinition) {
        final List<? extends EventDefinition> eventDefinitions = portletApplicationDefinition.getEventDefinitions();
        if (eventDefinitions == null || eventDefinitions.isEmpty()) {
            return Collections.emptySet();
        }

        final String defaultNamespace = portletApplicationDefinition.getDefaultNamespace();

        for (final EventDefinition eventDefinition : eventDefinitions) {
            final QName defQName = eventDefinition.getQualifiedName(defaultNamespace);
            if (defQName != null && defQName.equals(eventName)) {
                final List<QName> aliases = eventDefinition.getAliases();
                if (aliases == null || aliases.isEmpty()) {
                    return Collections.emptySet();
                }

                return new LinkedHashSet<QName>(aliases);
            }
        }

        return Collections.emptySet();
    }

    protected boolean supportsEvent(Event event, IPortletDefinitionId portletDefinitionId) {
        final QName eventName = event.getQName();

        //The cache key to use
        final Tuple<IPortletDefinitionId, QName> key = new Tuple<IPortletDefinitionId, QName>(portletDefinitionId,
                eventName);

        //Check in the cache if the portlet definition supports this event
        final Element element = this.supportedEventCache.get(key);
        if (element != null) {
            final Boolean supported = (Boolean) element.getValue();
            if (supported != null) {
                return supported;
            }
        }

        final PortletApplicationDefinition portletApplicationDescriptor = this.portletDefinitionRegistry
                .getParentPortletApplicationDescriptor(portletDefinitionId);
        if (portletApplicationDescriptor == null) {
            return false;
        }

        final Set<QName> aliases = this.getAllAliases(eventName, portletApplicationDescriptor);

        final String defaultNamespace = portletApplicationDescriptor.getDefaultNamespace();

        //No support found so far, do more complex namespace matching
        final PortletDefinition portletDescriptor = this.portletDefinitionRegistry
                .getParentPortletDescriptor(portletDefinitionId);
        if (portletDescriptor == null) {
            return false;
        }

        final List<? extends EventDefinitionReference> supportedProcessingEvents = portletDescriptor
                .getSupportedProcessingEvents();
        for (final EventDefinitionReference eventDefinitionReference : supportedProcessingEvents) {
            final QName qualifiedName = eventDefinitionReference.getQualifiedName(defaultNamespace);
            if (qualifiedName == null) {
                continue;
            }

            //See if the supported qname and event qname match explicitly
            //Look for alias names
            if (qualifiedName.equals(eventName) || aliases.contains(qualifiedName)) {
                this.supportedEventCache.put(new Element(key, Boolean.TRUE));
                return true;
            }

            //Look for namespaced events
            if (StringUtils.isEmpty(qualifiedName.getNamespaceURI())) {
                final QName namespacedName = new QName(defaultNamespace, qualifiedName.getLocalPart());
                if (eventName.equals(namespacedName)) {
                    this.supportedEventCache.put(new Element(key, Boolean.TRUE));
                    return true;
                }
            }
        }

        this.supportedEventCache.put(new Element(key, Boolean.FALSE));
        return false;
    }
}