org.jasig.portal.layout.dlm.FragmentActivator.java Source code

Java tutorial

Introduction

Here is the source code for org.jasig.portal.layout.dlm.FragmentActivator.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.layout.dlm;

import java.io.Serializable;
import java.util.List;
import java.util.Locale;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.regex.Pattern;

import net.sf.ehcache.Ehcache;
import net.sf.ehcache.constructs.blocking.CacheEntryFactory;
import net.sf.ehcache.constructs.blocking.SelfPopulatingCache;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.jasig.portal.AuthorizationException;
import org.jasig.portal.IUserIdentityStore;
import org.jasig.portal.IUserProfile;
import org.jasig.portal.UserProfile;
import org.jasig.portal.i18n.LocaleManager;
import org.jasig.portal.layout.IUserLayoutStore;
import org.jasig.portal.properties.PropertiesManager;
import org.jasig.portal.security.IPerson;
import org.jasig.portal.security.provider.PersonImpl;
import org.jasig.portal.utils.Tuple;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.stereotype.Service;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;

import com.google.common.cache.CacheBuilder;
import com.google.common.cache.CacheLoader;
import com.google.common.cache.LoadingCache;

/**
 * @version $Revision$ $Date$
 * @since uPortal 2.5
 */
@Service
public class FragmentActivator {
    private static final String NEWLY_CREATED_ATTR = "newlyCreated";
    public static final String RCS_ID = "@(#) $Header$";
    private static final Log LOG = LogFactory.getLog(FragmentActivator.class);

    private final LoadingCache<String, List<Locale>> fragmentOwnerLocales = CacheBuilder.newBuilder()
            .<String, List<Locale>>build(new CacheLoader<String, List<Locale>>() {
                @Override
                public List<Locale> load(String key) throws Exception {
                    return new CopyOnWriteArrayList<Locale>();
                }
            });

    private Ehcache userViews;
    private Ehcache userViewErrors;
    private IUserIdentityStore identityStore;
    private IUserLayoutStore userLayoutStore;
    private ConfigurationLoader configurationLoader;

    private static final String PROPERTY_ALLOW_EXPANDED_CONTENT = "org.jasig.portal.layout.dlm.allowExpandedContent";
    private static final Pattern STANDARD_PATTERN = Pattern.compile("\\A[Rr][Ee][Gg][Uu][Ll][Aa][Rr]\\z");
    private static final Pattern EXPANDED_PATTERN = Pattern.compile(".*");

    @Autowired
    public void setUserViewErrors(
            @Qualifier("org.jasig.portal.layout.dlm.FragmentActivator.userViewErrors") Ehcache userViewErrors) {
        this.userViewErrors = userViewErrors;
    }

    @Autowired
    public void setUserViews(
            @Qualifier("org.jasig.portal.layout.dlm.FragmentActivator.userViews") Ehcache userViews) {
        this.userViews = new SelfPopulatingCache(userViews, new CacheEntryFactory() {
            @Override
            public Object createEntry(Object key) throws Exception {
                final UserViewKey userViewKey = (UserViewKey) key;

                //Check if there was an exception the last time a load attempt was made and re-throw
                final net.sf.ehcache.Element exceptionElement = userViewErrors.get(userViewKey);
                if (exceptionElement != null) {
                    throw (Exception) exceptionElement.getObjectValue();
                }

                try {
                    return activateFragment(userViewKey);
                } catch (Exception e) {
                    userViewErrors.put(new net.sf.ehcache.Element(userViewKey, e));
                    throw e;
                }
            }
        });
    }

    @Autowired
    public void setConfigurationLoader(ConfigurationLoader configurationLoader) {
        this.configurationLoader = configurationLoader;
    }

    @Autowired
    public void setIdentityStore(IUserIdentityStore identityStore) {
        this.identityStore = identityStore;
    }

    @Autowired
    public void setUserLayoutStore(IUserLayoutStore userLayoutStore) {
        this.userLayoutStore = userLayoutStore;
    }

    private static class UserViewKey implements Serializable {
        private static final long serialVersionUID = 1L;
        private final String ownerId;
        private final Locale locale;
        private final int hashCode;

        public UserViewKey(String ownerId, Locale locale) {
            this.ownerId = ownerId;
            this.locale = locale;
            this.hashCode = internalHashCode();
        }

        public String getOwnerId() {
            return ownerId;
        }

        public Locale getLocale() {
            return locale;
        }

        @Override
        public int hashCode() {
            return this.hashCode;
        }

        public int internalHashCode() {
            final int prime = 31;
            int result = 1;
            result = prime * result + ((ownerId == null) ? 0 : ownerId.hashCode());
            result = prime * result + ((locale == null) ? 0 : locale.hashCode());
            return result;
        }

        @Override
        public boolean equals(Object obj) {
            if (this == obj)
                return true;
            if (obj == null)
                return false;
            if (getClass() != obj.getClass())
                return false;
            UserViewKey other = (UserViewKey) obj;
            if (ownerId == null) {
                if (other.ownerId != null)
                    return false;
            } else if (!ownerId.equals(other.ownerId))
                return false;
            if (locale == null) {
                if (other.locale != null)
                    return false;
            } else if (!locale.equals(other.locale))
                return false;
            return true;
        }

        @Override
        public String toString() {
            return "UserViewKey [ownerId=" + ownerId + ", locale=" + locale + "]";
        }
    }

    private UserView activateFragment(final UserViewKey userViewKey) {
        final String ownerId = userViewKey.getOwnerId();
        final FragmentDefinition fd = configurationLoader.getFragmentByOwnerId(ownerId);

        final Locale locale = userViewKey.getLocale();

        fragmentOwnerLocales.getUnchecked(ownerId).add(locale);

        if (fd.isNoAudienceIncluded()) {
            if (LOG.isDebugEnabled()) {
                LOG.debug("Skipping activation of FragmentDefinition " + fd.getName() + ", no evaluators found. "
                        + fd);
            }

            return null;
        }

        if (LOG.isDebugEnabled()) {
            LOG.debug("Activating FragmentDefinition " + fd.getName() + " with locale " + locale);
        }

        IPerson owner = bindToOwner(fd);
        UserView view = new UserView(owner.getID());
        loadLayout(view, fd, owner, locale);

        // if owner just created we need to push the layout into
        // the db so that our fragment template user is used and
        // not the default template user as determined by
        // the user identity store.
        if (owner.getAttribute(NEWLY_CREATED_ATTR) != null) {
            owner.setAttribute(Constants.PLF, view.layout);
            try {
                saveLayout(view, owner);
            } catch (Exception e) {
                throw new RuntimeException(
                        "Failed to save layout for newly created fragment owner " + owner.getUserName(), e);
            }
        }

        loadPreferences(view, fd);
        fragmentizeLayout(view, fd);

        if (LOG.isInfoEnabled()) {
            LOG.info("Activated FragmentDefinition " + fd.getName() + " with locale " + locale);
        }
        return view;
    }

    public UserView getUserView(final FragmentDefinition fd, final Locale locale) {
        final UserViewKey userViewKey = new UserViewKey(fd.getOwnerId(), locale);
        final net.sf.ehcache.Element userViewElement = this.userViews.get(userViewKey);
        return (UserView) userViewElement.getObjectValue();
    }

    public boolean hasUserView(FragmentDefinition fd, Locale locale) {

        // Assertions...
        if (fd == null) {
            String msg = "Argument 'fd' [FragmentDefinition] cannot be null.";
            throw new IllegalArgumentException(msg);
        }

        return getUserView(fd.getOwnerId(), locale) != null;

    }

    /**
     * Saves the loaded layout in the database for the user and profile.
     * @param view
     * @param owner
     * @throws Exception
     */
    private void saveLayout(UserView view, IPerson owner) throws Exception {
        IUserProfile profile = new UserProfile();
        profile.setProfileId(view.profileId);
        userLayoutStore.setUserLayout(owner, profile, view.layout, true, false);
    }

    private IPerson bindToOwner(FragmentDefinition fragment) {
        IPerson owner = new PersonImpl();
        owner.setAttribute("username", fragment.getOwnerId());
        int userID = -1;

        try {
            userID = identityStore.getPortalUID(owner, false);
        } catch (AuthorizationException ae) {
            // current implementation of RDMBUserIdentityStore throws an
            // auth exception if the user doesn't exist even if 
            // create data is false as we have it here. So this exception
            // can be discarded since we check for the userID being -1
            // meaning that the user wasn't found to trigger creating
            // that user.
        }
        if (userID == -1) {
            userID = createOwner(owner, fragment);
            owner.setAttribute(NEWLY_CREATED_ATTR, "" + (userID != -1));
        }

        owner.setID(userID);
        return owner;
    }

    private int createOwner(IPerson owner, FragmentDefinition fragment) {
        String defaultUser = null;
        int userID = -1;

        if (fragment.defaultLayoutOwnerID != null) {
            defaultUser = fragment.defaultLayoutOwnerID;
        } else {
            final String defaultLayoutOwner = PropertiesManager
                    .getProperty("org.jasig.portal.layout.dlm.defaultLayoutOwner");
            if (defaultLayoutOwner != null) {
                defaultUser = defaultLayoutOwner;
            } else {
                try {
                    defaultUser = PropertiesManager.getProperty(RDBMDistributedLayoutStore.TEMPLATE_USER_NAME);
                } catch (RuntimeException re) {
                    throw new RuntimeException("\n\n WARNING: defaultLayoutOwner is not specified"
                            + " in dlm.xml and no default user is configured for " + "the system. Owner '"
                            + fragment.getOwnerId() + "' for " + "fragment '" + fragment.getName() + "' can not be "
                            + "created. The fragment will not be available for " + "inclusion into user layouts.\n",
                            re);
                }
            }
        }

        if (LOG.isDebugEnabled()) {
            LOG.debug("\n\nOwner '" + fragment.getOwnerId() + "' of fragment '" + fragment.getName()
                    + "' not found. Creating as copy of '" + defaultUser + "'\n");
        }

        if (defaultUser != null) {
            owner.setAttribute("uPortalTemplateUserName", defaultUser);
        }

        try {
            userID = identityStore.getPortalUID(owner, true);
        } catch (AuthorizationException ae) {
            throw new RuntimeException("\n\nWARNING: Anomaly occurred while creating owner '"
                    + fragment.getOwnerId() + "' of fragment '" + fragment.getName()
                    + "'. The fragment will not be " + "available for inclusion into user layouts.", ae);
        }
        return userID;
    }

    private void loadLayout(UserView view, FragmentDefinition fragment, IPerson owner, Locale locale) {
        // if fragment not bound to user can't return any layouts.
        if (view.getUserId() == -1)
            return;

        // this area is hacked right now. Time won't permit how to handle
        // matching up multiple profiles for a fragment with an appropriate
        // one for incorporating into a user's layout based on their profile
        // when they log in with a certain user agent. The challenge is
        // being able to match up profiles for a user with those of a 
        // fragment. Until this is resolved only one profile will be supported
        // and will have a hard coded id of 1 which is the default for profiles.
        // If anyone changes this user all heck could break loose for dlm. :-(

        Document layout = null;

        try {
            // fix hard coded 1 later for multiple profiles
            IUserProfile profile = userLayoutStore.getUserProfileByFname(owner, "default");
            profile.setLocaleManager(new LocaleManager(owner, new Locale[] { locale }));

            // see if we have structure & theme stylesheets for this user yet.
            // If not then fall back on system's selected stylesheets.
            if (profile.getStructureStylesheetId() == 0 || profile.getThemeStylesheetId() == 0)
                profile = userLayoutStore.getSystemProfileByFname(profile.getProfileFname());

            view.profileId = profile.getProfileId();
            view.profileFname = profile.getProfileFname();
            view.layoutId = profile.getLayoutId();
            //            view.structureStylesheetId = profile.getStructureStylesheetId();
            //            view.themeStylesheetId = profile.getThemeStylesheetId();

            layout = userLayoutStore.getFragmentLayout(owner, profile);
            Element root = layout.getDocumentElement();
            root.setAttribute(Constants.ATT_ID, Constants.FRAGMENT_ID_USER_PREFIX + view.getUserId()
                    + Constants.FRAGMENT_ID_LAYOUT_PREFIX + view.layoutId);
            view.layout = layout;
        } catch (Exception e) {
            throw new RuntimeException("Anomaly occurred while loading layout for fragment '" + fragment.getName()
                    + "'. The fragment will not be " + "available for inclusion into user layouts.", e);
        }
    }

    private void loadPreferences(UserView view, FragmentDefinition fragment) {
        // if fragment not bound to user can't return any preferences.
        if (view.getUserId() == -1)
            return;

        IPerson p = new PersonImpl();
        p.setID(view.getUserId());
        p.setAttribute("username", fragment.getOwnerId());
    }

    /**
     * Removes unwanted and hidden folders, then changes all node ids to their 
     * globally safe incorporated version.
     */
    private void fragmentizeLayout(UserView view, FragmentDefinition fragment) {
        // if fragment not bound to user or layout empty due to error, return
        if (view.getUserId() == -1 || view.layout == null)
            return;

        // Choose what types of content to apply from the fragment
        Pattern contentPattern = STANDARD_PATTERN; // default
        boolean allowExpandedContent = Boolean
                .parseBoolean(PropertiesManager.getProperty(PROPERTY_ALLOW_EXPANDED_CONTENT));
        if (allowExpandedContent) {
            contentPattern = EXPANDED_PATTERN;
        }

        // remove all non-regular or hidden top level folders
        // skip root folder that is only child of top level layout element
        Element layout = view.layout.getDocumentElement();
        Element root = (Element) layout.getFirstChild();
        NodeList children = root.getChildNodes();

        // process the children backwards since as we delete some the indices
        // shift around
        for (int i = children.getLength() - 1; i >= 0; i--) {
            Node node = children.item(i);
            if (node.getNodeType() == Node.ELEMENT_NODE && node.getNodeName().equals("folder")) {
                Element folder = (Element) node;

                // strip out folder types 'header', 'footer' and regular, 
                // hidden folder "User Preferences" since users have their own
                boolean isApplicable = contentPattern.matcher(folder.getAttribute("type")).matches();
                if (!isApplicable || folder.getAttribute("hidden").equals("true")) {
                    try {
                        root.removeChild(folder);
                    } catch (Exception e) {
                        throw new RuntimeException("Anomaly occurred while stripping out "
                                + " portions of layout for fragment '" + fragment.getName()
                                + "'. The fragment will not be available for " + "inclusion into user layouts.", e);
                    }
                }
            }
        }
        // now re-lable all remaining nodes below root to have a safe system
        // wide id.

        setIdsAndAttribs(layout, layout.getAttribute(Constants.ATT_ID), "" + fragment.getIndex(),
                "" + fragment.getPrecedence());
    }

    /**
     * Recursive method that passes through a layout tree and changes all ids
     * from the regular format of sXX or nXX to the globally safe incorporated
     * id of form uXlXsXX or uXlXnXX indicating the user id and layout id from
     * which this node came.
     */
    private void setIdsAndAttribs(Element parent, String labelBase, String index, String precedence) {
        NodeList children = parent.getChildNodes();

        for (int i = 0; i < children.getLength(); i++) {
            if (children.item(i).getNodeType() == Node.ELEMENT_NODE) {
                Element child = (Element) children.item(i);
                String id = child.getAttribute(Constants.ATT_ID);
                if (!id.equals("")) {
                    String newId = labelBase + id;
                    child.setAttribute(Constants.ATT_ID, newId);
                    child.setIdAttribute(Constants.ATT_ID, true);
                    child.setAttributeNS(Constants.NS_URI, Constants.ATT_FRAGMENT, index);
                    child.setAttributeNS(Constants.NS_URI, Constants.ATT_PRECEDENCE, precedence);
                    setIdsAndAttribs(child, labelBase, index, precedence);
                }
            }
        }
    }

    private net.sf.ehcache.Element getUserView(String ownerId, Locale locale) {
        return userViews.get(new Tuple<String, String>(ownerId, locale.toString()));
    }

    public void clearChacheForOwner(final String ownerId) {
        final List<Locale> locales = fragmentOwnerLocales.getIfPresent(ownerId);
        if (locales == null) {
            //Nothing to purge
            return;
        }

        for (final Locale locale : locales) {
            final UserViewKey userViewKey = new UserViewKey(ownerId, locale);
            userViews.remove(userViewKey);
        }
    }
}