org.apereo.portal.layout.dlm.RDBMDistributedLayoutStore.java Source code

Java tutorial

Introduction

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

import java.io.StringWriter;
import java.sql.PreparedStatement;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.Iterator;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import java.util.regex.Pattern;

import org.apache.commons.lang.StringUtils;
import org.dom4j.Namespace;
import org.dom4j.io.DOMReader;
import org.dom4j.io.DOMWriter;
import org.dom4j.io.DocumentSource;
import org.dom4j.io.OutputFormat;
import org.dom4j.io.XMLWriter;
import org.apereo.portal.AuthorizationException;
import org.apereo.portal.IUserIdentityStore;
import org.apereo.portal.IUserProfile;
import org.apereo.portal.PortalException;
import org.apereo.portal.jdbc.RDBMServices;
import org.apereo.portal.i18n.LocaleManager;
import org.apereo.portal.io.xml.IPortalDataHandlerService;
import org.apereo.portal.layout.LayoutStructure;
import org.apereo.portal.layout.StructureParameter;
import org.apereo.portal.layout.StylesheetUserPreferencesImpl;
import org.apereo.portal.layout.dao.IStylesheetUserPreferencesDao;
import org.apereo.portal.layout.om.IStylesheetDescriptor;
import org.apereo.portal.layout.om.IStylesheetUserPreferences;
import org.apereo.portal.layout.simple.RDBMUserLayoutStore;
import org.apereo.portal.portlet.dao.IPortletEntityDao;
import org.apereo.portal.portlet.dao.jpa.PortletPreferenceImpl;
import org.apereo.portal.portlet.om.IPortletDefinition;
import org.apereo.portal.portlet.om.IPortletDefinitionId;
import org.apereo.portal.portlet.om.IPortletDefinitionParameter;
import org.apereo.portal.portlet.om.IPortletEntity;
import org.apereo.portal.portlet.om.IPortletPreference;
import org.apereo.portal.portlet.registry.IPortletEntityRegistry;
import org.apereo.portal.properties.PropertiesManager;
import org.apereo.portal.security.IPerson;
import org.apereo.portal.security.provider.BrokenSecurityContext;
import org.apereo.portal.security.provider.PersonImpl;
import org.apereo.portal.utils.DocumentFactory;
import org.apereo.portal.utils.IFragmentDefinitionUtils;
import org.apereo.portal.utils.MapPopulator;
import org.apereo.portal.utils.Tuple;
import org.apereo.portal.xml.XmlUtilitiesImpl;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.dao.DataRetrievalFailureException;
import org.springframework.dao.EmptyResultDataAccessException;
import org.springframework.transaction.annotation.Transactional;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.NamedNodeMap;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;

import com.google.common.cache.Cache;

import net.sf.ehcache.Ehcache;

/**
 * This class extends RDBMUserLayoutStore and implements instantiating and
 * storing layouts that conform to the design of the distribute layout
 * management system. These layouts consist of two types: layout fragments
 * that are the layouts owned by a user specified in dlm.xml, and composite
 * view layouts which represent regular users with zero or more UI elements
 * incorporated from layout fragments. Only a user's personal layout fragment
 * is
 *
 * @since uPortal 2.5
 */
public class RDBMDistributedLayoutStore extends RDBMUserLayoutStore {

    private static final Pattern VALID_PATHREF_PATTERN = Pattern.compile(".+\\:/.+");
    private static final String BAD_PATHREF_MESSAGE = "## DLM: ORPHANED DATA ##";

    private String systemDefaultUser = null;
    private boolean systemDefaultUserLoaded = false;

    private FragmentActivator fragmentActivator;

    private Ehcache fragmentNodeInfoCache;

    private boolean errorOnMissingPortlet = true;
    private boolean errorOnMissingUser = true;

    static final String TEMPLATE_USER_NAME = "org.apereo.portal.services.Authentication.defaultTemplateUserName";

    // Used in Import/Export operations
    private final org.dom4j.DocumentFactory fac = new org.dom4j.DocumentFactory();
    private final ThreadLocal<DOMReader> reader = new ThreadLocal<DOMReader>() {
        @Override
        protected DOMReader initialValue() {
            return new DOMReader();
        }
    };
    private final ThreadLocal<DOMWriter> writer = new ThreadLocal<DOMWriter>() {
        @Override
        protected DOMWriter initialValue() {
            return new DOMWriter();
        }
    };
    private IUserIdentityStore userIdentityStore;
    private IStylesheetUserPreferencesDao stylesheetUserPreferencesDao;
    private IPortletEntityRegistry portletEntityRegistry;
    private IPortletEntityDao portletEntityDao;
    private IPortalDataHandlerService portalDataHandlerService;
    private IFragmentDefinitionUtils fragmentUtils;

    @Autowired
    private NodeReferenceFactory nodeReferenceFactory;

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

    @Autowired
    public void setPortalDataHandlerService(IPortalDataHandlerService portalDataHandlerService) {
        this.portalDataHandlerService = portalDataHandlerService;
    }

    @Autowired
    public void setPortletEntityDao(@Qualifier("transient") IPortletEntityDao portletEntityDao) {
        this.portletEntityDao = portletEntityDao;
    }

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

    @Autowired
    public void setStylesheetUserPreferencesDao(IStylesheetUserPreferencesDao stylesheetUserPreferencesDao) {
        this.stylesheetUserPreferencesDao = stylesheetUserPreferencesDao;
    }

    @Autowired
    public void setFragmentDefinitionUtils(IFragmentDefinitionUtils utils) {
        this.fragmentUtils = utils;
    }

    @Autowired
    public void setFragmentNodeInfoCache(
            @Qualifier("org.apereo.portal.layout.dlm.RDBMDistributedLayoutStore.fragmentNodeInfoCache") Ehcache fragmentNodeInfoCache) {
        this.fragmentNodeInfoCache = fragmentNodeInfoCache;
    }

    @Value("${org.apereo.portal.io.layout.errorOnMissingPortlet:true}")
    public void setErrorOnMissingPortlet(boolean errorOnMissingPortlet) {
        this.errorOnMissingPortlet = errorOnMissingPortlet;
    }

    @Value("${org.apereo.portal.io.layout.errorOnMissingUser:true}")
    public void setErrorOnMissingUser(boolean errorOnMissingUser) {
        this.errorOnMissingUser = errorOnMissingUser;
    }

    /**
     * Method for acquiring copies of fragment layouts to assist in debugging.
     * No infrastructure code calls this but channels designed to expose the
     * structure of the cached fragments use this to obtain copies.
     * @return Map
     */
    public Map<String, Document> getFragmentLayoutCopies() {
        // since this is only visible in fragment list in administrative portlet, use default portal locale
        final Locale defaultLocale = LocaleManager.getPortalLocales()[0];

        final Map<String, Document> layouts = new HashMap<String, Document>();

        final List<FragmentDefinition> definitions = this.fragmentUtils.getFragmentDefinitions();
        for (final FragmentDefinition fragmentDefinition : definitions) {
            final Document layout = DocumentFactory.getThreadDocument();
            final UserView userView = this.fragmentUtils.getUserView(fragmentDefinition, defaultLocale);
            if (userView == null) {
                logger.warn("No UserView found for FragmentDefinition {}, it will be skipped.",
                        fragmentDefinition.getName());
                continue;
            }
            final Node copy = layout.importNode(userView.getLayout().getDocumentElement(), true);
            layout.appendChild(copy);
            layouts.put(fragmentDefinition.getOwnerId(), layout);
        }
        return layouts;
    }

    @Autowired
    public void setFragmentActivator(FragmentActivator fragmentActivator) {
        this.fragmentActivator = fragmentActivator;
    }

    private IStylesheetUserPreferences loadDistributedStylesheetUserPreferences(IPerson person,
            IUserProfile profile, long stylesheetDescriptorId, Set<String> fragmentNames) {
        final boolean isFragmentOwner = this.isFragmentOwner(person);

        final Locale locale = profile.getLocaleManager().getLocales()[0];
        final IStylesheetDescriptor stylesheetDescriptor = this.stylesheetDescriptorDao
                .getStylesheetDescriptor(stylesheetDescriptorId);
        final IStylesheetUserPreferences stylesheetUserPreferences = this.stylesheetUserPreferencesDao
                .getStylesheetUserPreferences(stylesheetDescriptor, person, profile);

        final IStylesheetUserPreferences distributedStylesheetUserPreferences = new StylesheetUserPreferencesImpl();

        for (final String fragName : fragmentNames) {
            final FragmentDefinition fragmentDefinition = this.fragmentUtils.getFragmentDefinitionByName(fragName);

            //UserView may be missing if the fragment isn't defined correctly
            final UserView userView = this.fragmentUtils.getUserView(fragmentDefinition, locale);
            if (userView == null) {
                logger.warn(
                        "No UserView is present for fragment {} it will be skipped when loading distributed stylesheet user preferences",
                        fragmentDefinition.getName());
                continue;
            }

            //IStylesheetUserPreferences only exist if something was actually set
            final IStylesheetUserPreferences fragmentStylesheetUserPreferences = this.stylesheetUserPreferencesDao
                    .getStylesheetUserPreferences(stylesheetDescriptor, userView.getUserId(),
                            userView.getProfileId());
            if (fragmentStylesheetUserPreferences == null) {
                continue;
            }

            //Get the info needed to DLMify node IDs
            final Element root = userView.getLayout().getDocumentElement();
            final String labelBase = root.getAttribute(Constants.ATT_ID);

            boolean modified = false;

            // Copy all of the fragment preferences into the distributed preferences
            final Collection<String> allLayoutAttributeNodeIds = fragmentStylesheetUserPreferences
                    .getAllLayoutAttributeNodeIds();
            for (final String fragmentNodeId : allLayoutAttributeNodeIds) {
                final String userNodeId = (isFragmentOwner
                        || fragmentNodeId.startsWith(Constants.FRAGMENT_ID_USER_PREFIX)) ? fragmentNodeId
                                : labelBase + fragmentNodeId;

                final MapPopulator<String, String> layoutAttributesPopulator = new MapPopulator<String, String>();
                fragmentStylesheetUserPreferences.populateLayoutAttributes(fragmentNodeId,
                        layoutAttributesPopulator);
                final Map<String, String> layoutAttributes = layoutAttributesPopulator.getMap();
                for (final Map.Entry<String, String> layoutAttributesEntry : layoutAttributes.entrySet()) {
                    final String name = layoutAttributesEntry.getKey();
                    final String value = layoutAttributesEntry.getValue();

                    // Fragmentize the nodeId here
                    distributedStylesheetUserPreferences.setLayoutAttribute(userNodeId, name, value);

                    // Clean out user preferences data that matches data from the fragment.
                    // Skip for fragment owners since their user preference data and the fragment stylesheet user prefs
                    // are identical and removing layout attributes here would affect the fragment layout.
                    if (stylesheetUserPreferences != null && !isFragmentOwner) {
                        final String userValue = stylesheetUserPreferences.getLayoutAttribute(userNodeId, name);
                        if (userValue != null && userValue.equals(value)) {
                            stylesheetUserPreferences.removeLayoutAttribute(userNodeId, name);
                            EditManager.removePreferenceDirective(person, userNodeId, name);
                            modified = true;
                        }
                    }
                }
            }

            if (modified) {
                this.stylesheetUserPreferencesDao.storeStylesheetUserPreferences(stylesheetUserPreferences);
            }
        }

        return distributedStylesheetUserPreferences;
    }

    @Override
    public double getFragmentPrecedence(int index) {
        final List<FragmentDefinition> definitions = this.fragmentUtils.getFragmentDefinitions();
        if (index < 0 || index > definitions.size() - 1) {
            return 0;
        }

        // must pass through the array looking for the fragment with this
        // index since the array was sorted by precedence and then index
        // within precedence.
        for (final FragmentDefinition fragmentDefinition : definitions) {
            if (fragmentDefinition.getIndex() == index) {
                return fragmentDefinition.getPrecedence();
            }
        }
        return 0; // should never get here.
    }

    /**
       Returns the layout for a user decorated with any specified decorator.
       The layout returned is a composite layout for non fragment owners
       and a regular layout for layout owners. A composite layout is made up
       of layout pieces from potentially multiple incorporated layouts. If
       no layouts are defined then the composite layout will be the same as
       the user's personal layout fragment or PLF, the one holding only those
       UI elements that they own or incorporated elements that they have been
       allowed to changed.
     */
    @Override
    public DistributedUserLayout getUserLayout(IPerson person, IUserProfile profile)

    {

        final DistributedUserLayout layout = this._getUserLayout(person, profile);

        return layout;
    }

    private boolean layoutExistsForUser(IPerson person) {

        // Assertions.
        if (person == null) {
            final String msg = "Argument 'person' cannot be null.";
            throw new IllegalArgumentException(msg);
        }

        final int struct_count = this.jdbcOperations.queryForObject(
                "SELECT COUNT(*) FROM up_layout_struct WHERE user_id = ?", Integer.class, person.getID());
        return struct_count == 0 ? false : true;

    }

    @Override
    public org.dom4j.Element exportLayout(IPerson person, IUserProfile profile) {
        org.dom4j.Element layout = getExportLayoutDom(person, profile);

        final int userId = person.getID();
        final String userName = person.getUserName();
        final Set<IPortletEntity> portletEntities = this.portletEntityDao.getPortletEntitiesForUser(userId);

        org.dom4j.Element preferencesElement = null;
        for (final Iterator<IPortletEntity> entityItr = portletEntities.iterator(); entityItr.hasNext();) {
            final IPortletEntity portletEntity = entityItr.next();
            final List<IPortletPreference> preferencesList = portletEntity.getPortletPreferences();

            //Only bother with entities that have preferences
            if (!preferencesList.isEmpty()) {
                final String layoutNodeId = portletEntity.getLayoutNodeId();
                final Pathref dlmPathref = nodeReferenceFactory.getPathrefFromNoderef(userName, layoutNodeId,
                        layout);
                if (dlmPathref == null) {
                    logger.warn(
                            "{} in user {}'s layout has no corresponding layout or portlet information and will be ignored",
                            portletEntity, userName);
                    continue;
                }

                for (final IPortletPreference portletPreference : preferencesList) {
                    if (preferencesElement == null) {
                        if (layout == null) {
                            final org.dom4j.Document layoutDoc = new org.dom4j.DocumentFactory().createDocument();
                            layout = layoutDoc.addElement("layout");
                            layout.addNamespace("dlm", Constants.NS_URI);
                        }
                        preferencesElement = layout.addElement("preferences");
                    }

                    final org.dom4j.Element preferenceEntry = preferencesElement.addElement("entry");
                    preferenceEntry.addAttribute("entity", dlmPathref.toString());
                    preferenceEntry.addAttribute("channel", dlmPathref.getPortletFname());
                    preferenceEntry.addAttribute("name", portletPreference.getName());

                    for (final String value : portletPreference.getValues()) {
                        final org.dom4j.Element valueElement = preferenceEntry.addElement("value");
                        if (value != null) {
                            valueElement.setText(value);
                        }
                    }
                }
            }
        }

        if (layout != null) {
            layout.addAttribute("script", "classpath://org/jasig/portal/io/import-layout_v3-2.crn");
            layout.addAttribute("username", userName);
        }

        return layout;
    }

    private org.dom4j.Element getExportLayoutDom(IPerson person, IUserProfile profile) {
        if (!this.layoutExistsForUser(person)) {
            return null;
        }

        org.dom4j.Document layoutDoc = null;
        try {
            final Document layoutDom = this._safeGetUserLayout(person, profile);
            person.setAttribute(Constants.PLF, layoutDom);
            layoutDoc = this.reader.get().read(layoutDom);
        } catch (final Throwable t) {
            final String msg = "Unable to obtain layout & profile for user '" + person.getUserName()
                    + "', profileId " + profile.getProfileId();
            throw new RuntimeException(msg, t);
        }

        if (logger.isDebugEnabled()) {
            // Write out this version of the layout to the log for dev purposes...
            final StringWriter str = new StringWriter();
            final XMLWriter xml = new XMLWriter(str, new OutputFormat("  ", true));
            try {
                xml.write(layoutDoc);
                xml.close();
            } catch (final Throwable t) {
                throw new RuntimeException(
                        "Failed to write the layout for user '" + person.getUserName() + "' to the DEBUG log", t);
            }
            logger.debug("Layout for user: {}\n{}", person.getUserName(), str.getBuffer().toString());
        }

        /*
         * Attempt to detect a corrupted layout; return null in such cases
         */

        if (isLayoutCorrupt(layoutDoc)) {
            logger.warn("Layout for user: {} is corrupt; layout structures will not be exported.",
                    person.getUserName());
            return null;
        }

        /*
         * Clean up the DOM for export.
         */

        // (1) Add structure & theme attributes...
        final int structureStylesheetId = profile.getStructureStylesheetId();
        this.addStylesheetUserPreferencesAttributes(person, profile, layoutDoc, structureStylesheetId, "structure");

        final int themeStylesheetId = profile.getThemeStylesheetId();
        this.addStylesheetUserPreferencesAttributes(person, profile, layoutDoc, themeStylesheetId, "theme");

        // (2) Remove locale info...
        final Iterator<org.dom4j.Attribute> locale = (Iterator<org.dom4j.Attribute>) layoutDoc
                .selectNodes("//@locale").iterator();
        while (locale.hasNext()) {
            final org.dom4j.Attribute loc = locale.next();
            loc.getParent().remove(loc);
        }

        // (3) Scrub unnecessary channel information...
        for (final Iterator<org.dom4j.Element> orphanedChannels = (Iterator<org.dom4j.Element>) layoutDoc
                .selectNodes("//channel[@fname = '']").iterator(); orphanedChannels.hasNext();) {
            // These elements represent UP_LAYOUT_STRUCT rows where the 
            // CHAN_ID field was not recognized by ChannelRegistryStore;  
            // best thing to do is remove the elements...
            final org.dom4j.Element ch = orphanedChannels.next();
            ch.getParent().remove(ch);
        }
        final List<String> channelAttributeWhitelist = Arrays.asList(new String[] { "fname", "unremovable",
                "hidden", "immutable", "ID", "dlm:plfID", "dlm:moveAllowed", "dlm:deleteAllowed" });
        final Iterator<org.dom4j.Element> channels = (Iterator<org.dom4j.Element>) layoutDoc
                .selectNodes("//channel").iterator();
        while (channels.hasNext()) {
            final org.dom4j.Element oldCh = channels.next();
            final org.dom4j.Element parent = oldCh.getParent();
            final org.dom4j.Element newCh = this.fac.createElement("channel");
            for (final String aName : channelAttributeWhitelist) {
                final org.dom4j.Attribute a = (org.dom4j.Attribute) oldCh.selectSingleNode("@" + aName);
                if (a != null) {
                    newCh.addAttribute(a.getQName(), a.getValue());
                }
            }
            parent.elements().add(parent.elements().indexOf(oldCh), newCh);
            parent.remove(oldCh);
        }

        // (4) Convert internal DLM noderefs to external form (pathrefs)...
        for (final Iterator<org.dom4j.Attribute> origins = (Iterator<org.dom4j.Attribute>) layoutDoc
                .selectNodes("//@dlm:origin").iterator(); origins.hasNext();) {
            final org.dom4j.Attribute org = origins.next();
            final Pathref dlmPathref = this.nodeReferenceFactory.getPathrefFromNoderef(
                    (String) person.getAttribute(IPerson.USERNAME), org.getValue(), layoutDoc.getRootElement());
            if (dlmPathref != null) {
                // Change the value only if we have a valid pathref...
                org.setValue(dlmPathref.toString());
            } else {
                if (logger.isWarnEnabled()) {
                    logger.warn("Layout element '{}' from user '{}' failed to match noderef '{}'",
                            org.getUniquePath(), person.getAttribute(IPerson.USERNAME), org.getValue());
                }
            }
        }
        for (final Iterator<org.dom4j.Attribute> it = (Iterator<org.dom4j.Attribute>) layoutDoc
                .selectNodes("//@dlm:target").iterator(); it.hasNext();) {
            final org.dom4j.Attribute target = it.next();
            final Pathref dlmPathref = this.nodeReferenceFactory.getPathrefFromNoderef(
                    (String) person.getAttribute(IPerson.USERNAME), target.getValue(), layoutDoc.getRootElement());
            if (dlmPathref != null) {
                // Change the value only if we have a valid pathref...
                target.setValue(dlmPathref.toString());
            } else {
                if (logger.isWarnEnabled()) {
                    logger.warn("Layout element '{}' from user '{}' failed to match noderef '{}'",
                            target.getUniquePath(), person.getAttribute(IPerson.USERNAME), target.getValue());
                }
            }
        }
        for (final Iterator<org.dom4j.Attribute> names = (Iterator<org.dom4j.Attribute>) layoutDoc
                .selectNodes("//dlm:*/@name").iterator(); names.hasNext();) {
            final org.dom4j.Attribute n = names.next();
            if (n.getValue() == null || n.getValue().trim().length() == 0) {
                // Outer <dlm:positionSet> elements don't seem to use the name 
                // attribute, though their childern do.  Just skip these so we 
                // don't send a false WARNING.
                continue;
            }
            final Pathref dlmPathref = this.nodeReferenceFactory.getPathrefFromNoderef(
                    (String) person.getAttribute(IPerson.USERNAME), n.getValue(), layoutDoc.getRootElement());
            if (dlmPathref != null) {
                // Change the value only if we have a valid pathref...
                n.setValue(dlmPathref.toString());
                // These *may* have fnames...
                if (dlmPathref.getPortletFname() != null) {
                    n.getParent().addAttribute("fname", dlmPathref.getPortletFname());
                }
            } else {
                if (logger.isWarnEnabled()) {
                    logger.warn("Layout element '{}' from user '{}' failed to match noderef '{}'",
                            n.getUniquePath(), person.getAttribute(IPerson.USERNAME), n.getValue());
                }
            }
        }

        // Remove synthetic Ids, but from non-fragment owners only...
        if (!this.isFragmentOwner(person)) {

            /*
             * In the case of fragment owners, the original database Ids allow 
             * us keep (not break) the associations that subscribers have with 
             * nodes on the fragment layout.
             */

            // (5) Remove dlm:plfID...
            for (final Iterator<org.dom4j.Attribute> plfid = (Iterator<org.dom4j.Attribute>) layoutDoc
                    .selectNodes("//@dlm:plfID").iterator(); plfid.hasNext();) {
                final org.dom4j.Attribute plf = plfid.next();
                plf.getParent().remove(plf);
            }

            // (6) Remove database Ids...
            for (final Iterator<org.dom4j.Attribute> ids = (Iterator<org.dom4j.Attribute>) layoutDoc
                    .selectNodes("//@ID").iterator(); ids.hasNext();) {
                final org.dom4j.Attribute a = ids.next();
                a.getParent().remove(a);
            }
        }

        return layoutDoc.getRootElement();
    }

    /**
     * Attempts to detect known forms of corruption to avoid erroring-out on the 
     * export (or subsequent import), and also to prevent migrating a bad layout.  
     * Users whose layouts are culled in this fashion will have their layouts 
     * reset through migration.
     */
    private boolean isLayoutCorrupt(org.dom4j.Document layoutDoc) {

        boolean rslt = false; // until we find otherwise...

        for (FormOfLayoutCorruption form : KNOWN_FORMS_OF_LAYOUT_CORRUPTION) {
            if (form.detect(layoutDoc)) {
                logger.warn("Corrupt layout detected: {}", form.getMessage());
                rslt = true;
                break;
            }
        }

        return rslt;

    }

    private void addStylesheetUserPreferencesAttributes(IPerson person, IUserProfile profile,
            org.dom4j.Document layoutDoc, int stylesheetId, String attributeType) {
        final IStylesheetDescriptor structureStylesheetDescriptor = this.stylesheetDescriptorDao
                .getStylesheetDescriptor(stylesheetId);

        final IStylesheetUserPreferences ssup = this.stylesheetUserPreferencesDao
                .getStylesheetUserPreferences(structureStylesheetDescriptor, person, profile);

        if (ssup != null) {
            final Collection<String> allLayoutAttributeNodeIds = ssup.getAllLayoutAttributeNodeIds();
            for (final String nodeId : allLayoutAttributeNodeIds) {

                final MapPopulator<String, String> layoutAttributesPopulator = new MapPopulator<String, String>();
                ssup.populateLayoutAttributes(nodeId, layoutAttributesPopulator);
                final Map<String, String> layoutAttributes = layoutAttributesPopulator.getMap();

                final org.dom4j.Element element = layoutDoc.elementByID(nodeId);
                if (element == null) {
                    logger.warn(
                            "No node with id '{}' found in layout for: {}. Stylesheet user preference layout attributes will be ignored: {}",
                            nodeId, person.getUserName(), layoutAttributes);
                    continue;
                }

                for (final Entry<String, String> attributeEntry : layoutAttributes.entrySet()) {
                    final String name = attributeEntry.getKey();
                    final String value = attributeEntry.getValue();

                    logger.debug("Adding structure folder attribute:  name={}, value={}", name, value);

                    final org.dom4j.Element structAttrElement = this.fac
                            .createElement(attributeType + "-attribute");
                    final org.dom4j.Element nameAttribute = structAttrElement.addElement("name");
                    nameAttribute.setText(name);
                    final org.dom4j.Element valueAttribute = structAttrElement.addElement("value");
                    valueAttribute.setText(value);
                    element.elements().add(0, structAttrElement);
                }
            }
        } else {
            logger.debug("no StylesheetUserPreferences found for {}, {}", person, profile);
        }
    }

    @Override
    @SuppressWarnings("unchecked")
    @Transactional
    public void importLayout(org.dom4j.Element layout) {
        if (layout.getNamespaceForPrefix("dlm") == null) {
            layout.add(new Namespace("dlm", Constants.NS_URI));
        }

        //Remove comments from the DOM they break import
        final List<org.dom4j.Node> comments = layout.selectNodes("//comment()");
        for (final org.dom4j.Node comment : comments) {
            comment.detach();
        }

        //Get a ref to the prefs element and then remove it from the layout
        final org.dom4j.Node preferencesElement = layout.selectSingleNode("preferences");
        if (preferencesElement != null) {
            preferencesElement.getParent().remove(preferencesElement);
        }

        final String ownerUsername = layout.valueOf("@username");

        //Get a ref to the profile element and then remove it from the layout
        final org.dom4j.Node profileElement = layout.selectSingleNode("profile");
        if (profileElement != null) {
            profileElement.getParent().remove(profileElement);

            final org.dom4j.Document profileDocument = new org.dom4j.DocumentFactory().createDocument();
            profileDocument.setRootElement((org.dom4j.Element) profileElement);
            profileDocument.setName(ownerUsername + ".profile");

            final DocumentSource profileSource = new DocumentSource(profileElement);
            this.portalDataHandlerService.importData(profileSource);
        }

        final IPerson person = new PersonImpl();
        person.setUserName(ownerUsername);

        int ownerId;
        try {
            //Can't just pass true for create here, if the user actually exists the create flag also updates the user data
            ownerId = this.userIdentityStore.getPortalUID(person);
        } catch (final AuthorizationException t) {
            if (this.errorOnMissingUser) {
                throw new RuntimeException("Unrecognized user " + person.getUserName()
                        + "; you must import users before their layouts or set org.apereo.portal.io.layout.errorOnMissingUser to false.",
                        t);
            }

            //Create the missing user
            ownerId = this.userIdentityStore.getPortalUID(person, true);
        }

        if (ownerId == -1) {
            throw new RuntimeException("Unrecognized user " + person.getUserName()
                    + "; you must import users before their layouts or set org.apereo.portal.io.layout.errorOnMissingUser to false.");
        }
        person.setID(ownerId);

        IUserProfile profile = null;
        try {
            person.setSecurityContext(new BrokenSecurityContext());
            profile = this.getUserProfileByFname(person, "default");
        } catch (final Throwable t) {
            throw new RuntimeException("Failed to load profile for " + person.getUserName()
                    + "; This user must have a profile for import to continue.", t);
        }

        // (6) Add database Ids & (5) Add dlm:plfID ...
        int nextId = 1;
        for (final Iterator<org.dom4j.Element> it = (Iterator<org.dom4j.Element>) layout
                .selectNodes("folder | dlm:* | channel").iterator(); it.hasNext();) {
            nextId = this.addIdAttributesIfNecessary(it.next(), nextId);
        }
        // Now update UP_USER...
        this.jdbcOperations.update("UPDATE up_user SET next_struct_id = ? WHERE user_id = ?", nextId,
                person.getID());

        // (4) Convert external DLM pathrefs to internal form (noderefs)...
        for (final Iterator<org.dom4j.Attribute> itr = (Iterator<org.dom4j.Attribute>) layout
                .selectNodes("//@dlm:origin").iterator(); itr.hasNext();) {
            final org.dom4j.Attribute a = itr.next();
            final Noderef dlmNoderef = nodeReferenceFactory.getNoderefFromPathref(ownerUsername, a.getValue(), null,
                    true, layout);
            if (dlmNoderef != null) {
                // Change the value only if we have a valid pathref...
                a.setValue(dlmNoderef.toString());
                // For dlm:origin only, also use the noderef as the ID attribute...
                a.getParent().addAttribute("ID", dlmNoderef.toString());
            } else {
                // At least insure the value is between 1 and 35 characters
                a.setValue(BAD_PATHREF_MESSAGE);
            }
        }
        for (final Iterator<org.dom4j.Attribute> itr = (Iterator<org.dom4j.Attribute>) layout
                .selectNodes("//@dlm:target").iterator(); itr.hasNext();) {
            final org.dom4j.Attribute a = itr.next();
            final Noderef dlmNoderef = nodeReferenceFactory.getNoderefFromPathref(ownerUsername, a.getValue(), null,
                    true, layout);
            // Put in the correct value, or at least insure the value is between 1 and 35 characters
            a.setValue(dlmNoderef != null ? dlmNoderef.toString() : BAD_PATHREF_MESSAGE);
        }
        for (final Iterator<org.dom4j.Attribute> names = (Iterator<org.dom4j.Attribute>) layout
                .selectNodes("//dlm:*/@name").iterator(); names.hasNext();) {
            final org.dom4j.Attribute a = names.next();
            final String value = a.getValue().trim();
            if (!VALID_PATHREF_PATTERN.matcher(value).matches()) {
                /* Don't send it to getDlmNoderef if we know in advance it's not 
                 * going to work;  saves annoying/misleading log messages and 
                 * possibly some processing.  NOTE this is _only_ a problem with 
                 * the name attribute of some dlm:* elements, which seems to go 
                 * unused intentionally in some circumstances
                 */
                continue;
            }
            final org.dom4j.Attribute fname = a.getParent().attribute("fname");
            Noderef dlmNoderef = null;
            if (fname != null) {
                dlmNoderef = nodeReferenceFactory.getNoderefFromPathref(ownerUsername, value, fname.getValue(),
                        false, layout);
                // Remove the fname attribute now that we're done w/ it...
                fname.getParent().remove(fname);
            } else {
                dlmNoderef = nodeReferenceFactory.getNoderefFromPathref(ownerUsername, value, null, true, layout);
            }
            // Put in the correct value, or at least insure the value is between 1 and 35 characters
            a.setValue(dlmNoderef != null ? dlmNoderef.toString() : BAD_PATHREF_MESSAGE);
        }

        // (3) Restore chanID attributes on <channel> elements...
        for (final Iterator<org.dom4j.Element> it = (Iterator<org.dom4j.Element>) layout.selectNodes("//channel")
                .iterator(); it.hasNext();) {
            final org.dom4j.Element c = it.next();
            final String fname = c.valueOf("@fname");
            final IPortletDefinition cd = this.portletDefinitionRegistry.getPortletDefinitionByFname(fname);
            if (cd == null) {
                final String msg = "No portlet with fname=" + fname + " exists referenced by node "
                        + c.valueOf("@ID") + " from layout for " + ownerUsername;
                if (errorOnMissingPortlet) {
                    throw new IllegalArgumentException(msg);
                } else {
                    logger.warn(msg);
                    //Remove the bad channel node
                    c.getParent().remove(c);
                }
            } else {
                c.addAttribute("chanID", String.valueOf(cd.getPortletDefinitionId().getStringId()));
            }
        }

        // (2) Restore locale info...
        // (This step doesn't appear to be needed for imports)

        // (1) Process structure & theme attributes...
        Document layoutDom = null;
        try {

            final int structureStylesheetId = profile.getStructureStylesheetId();
            this.loadStylesheetUserPreferencesAttributes(person, profile, layout, structureStylesheetId,
                    "structure");

            final int themeStylesheetId = profile.getThemeStylesheetId();
            this.loadStylesheetUserPreferencesAttributes(person, profile, layout, themeStylesheetId, "theme");

            // From this point forward we need the user's PLF set as DLM expects it...
            for (final Iterator<org.dom4j.Text> it = (Iterator<org.dom4j.Text>) layout
                    .selectNodes("descendant::text()").iterator(); it.hasNext();) {
                // How many years have we used Java & XML, and this still isn't easy?
                final org.dom4j.Text txt = it.next();
                if (txt.getText().trim().length() == 0) {
                    txt.getParent().remove(txt);
                }
            }

            final org.dom4j.Element copy = layout.createCopy();
            final org.dom4j.Document doc = this.fac.createDocument(copy);
            doc.normalize();
            layoutDom = this.writer.get().write(doc);
            person.setAttribute(Constants.PLF, layoutDom);

        } catch (final Throwable t) {
            throw new RuntimeException("Unable to set UserPreferences for user:  " + person.getUserName(), t);
        }

        // Finally store the layout...
        try {
            this.setUserLayout(person, profile, layoutDom, true, true);
        } catch (final Throwable t) {
            final String msg = "Unable to persist layout for user:  " + ownerUsername;
            throw new RuntimeException(msg, t);
        }

        if (preferencesElement != null) {
            final int ownerUserId = this.userIdentityStore.getPortalUserId(ownerUsername);
            //TODO this assumes a single layout, when multi-layout support exists portlet entities will need to be re-worked to allow for a layout id to be associated with the entity

            //track which entities from the user's pre-existing set are touched (all non-touched entities will be removed)
            final Set<IPortletEntity> oldPortletEntities = new LinkedHashSet<IPortletEntity>(
                    this.portletEntityDao.getPortletEntitiesForUser(ownerUserId));

            final List<org.dom4j.Element> entries = preferencesElement.selectNodes("entry");
            for (final org.dom4j.Element entry : entries) {
                final String dlmPathRef = entry.attributeValue("entity");
                final String fname = entry.attributeValue("channel");
                final String prefName = entry.attributeValue("name");

                final Noderef dlmNoderef = nodeReferenceFactory.getNoderefFromPathref(person.getUserName(),
                        dlmPathRef, fname, false, layout);

                if (dlmNoderef != null && fname != null) {
                    final IPortletEntity portletEntity = this.getPortletEntity(fname, dlmNoderef.toString(),
                            ownerUserId);
                    oldPortletEntities.remove(portletEntity);

                    final List<IPortletPreference> portletPreferences = portletEntity.getPortletPreferences();

                    final List<org.dom4j.Element> valueElements = entry.selectNodes("value");
                    final List<String> values = new ArrayList<String>(valueElements.size());
                    for (final org.dom4j.Element valueElement : valueElements) {
                        values.add(valueElement.getText());
                    }

                    portletPreferences.add(
                            new PortletPreferenceImpl(prefName, false, values.toArray(new String[values.size()])));

                    this.portletEntityDao.updatePortletEntity(portletEntity);
                }
            }

            //Delete all portlet preferences for entities that were not imported
            for (final IPortletEntity portletEntity : oldPortletEntities) {
                portletEntity.setPortletPreferences(null);

                if (portletEntityRegistry.shouldBePersisted(portletEntity)) {
                    this.portletEntityDao.updatePortletEntity(portletEntity);
                } else {
                    this.portletEntityDao.deletePortletEntity(portletEntity);
                }
            }
        }
    }

    private IPortletEntity getPortletEntity(String fName, String layoutNodeId, int userId) {
        //Try getting the entity
        final IPortletEntity portletEntity = this.portletEntityDao.getPortletEntity(layoutNodeId, userId);
        if (portletEntity != null) {
            return portletEntity;
        }

        //Load the portlet definition
        final IPortletDefinition portletDefinition;
        try {
            portletDefinition = this.portletDefinitionRegistry.getPortletDefinitionByFname(fName);
        } catch (Exception e) {
            throw new DataRetrievalFailureException(
                    "Failed to retrieve ChannelDefinition for fName='" + fName + "'", e);
        }

        //The channel definition for the fName MUST exist for this class to function
        if (portletDefinition == null) {
            throw new EmptyResultDataAccessException("No ChannelDefinition exists for fName='" + fName + "'", 1);
        }

        //create the portlet entity
        final IPortletDefinitionId portletDefinitionId = portletDefinition.getPortletDefinitionId();
        return this.portletEntityDao.createPortletEntity(portletDefinitionId, layoutNodeId, userId);
    }

    private void loadStylesheetUserPreferencesAttributes(IPerson person, IUserProfile profile,
            org.dom4j.Element layout, final int structureStylesheetId, final String nodeType) {

        final IStylesheetDescriptor stylesheetDescriptor = this.stylesheetDescriptorDao
                .getStylesheetDescriptor(structureStylesheetId);
        final List<org.dom4j.Element> structureAttributes = layout.selectNodes("//" + nodeType + "-attribute");

        IStylesheetUserPreferences ssup = this.stylesheetUserPreferencesDao
                .getStylesheetUserPreferences(stylesheetDescriptor, person, profile);
        if (structureAttributes.isEmpty()) {
            if (ssup != null) {
                this.stylesheetUserPreferencesDao.deleteStylesheetUserPreferences(ssup);
            }
        } else {
            if (ssup == null) {
                ssup = this.stylesheetUserPreferencesDao.createStylesheetUserPreferences(stylesheetDescriptor,
                        person, profile);
            }

            final Map<String, Map<String, String>> oldLayoutAttributes = new HashMap<String, Map<String, String>>();
            for (final String nodeId : ssup.getAllLayoutAttributeNodeIds()) {
                final MapPopulator<String, String> nodeAttributes = new MapPopulator<String, String>();
                ssup.populateLayoutAttributes(nodeId, nodeAttributes);
                oldLayoutAttributes.put(nodeId, nodeAttributes.getMap());
            }

            for (final org.dom4j.Element structureAttribute : structureAttributes) {
                final org.dom4j.Element layoutElement = structureAttribute.getParent();
                final String nodeId = layoutElement.valueOf("@ID");
                if (StringUtils.isEmpty(nodeId)) {
                    logger.warn("@ID is empty for layout element, the attribute will be ignored: {}",
                            structureAttribute.asXML());
                }

                final String name = structureAttribute.valueOf("name");
                if (StringUtils.isEmpty(nodeId)) {
                    logger.warn("name is empty for layout element, the attribute will be ignored: {}",
                            structureAttribute.asXML());
                    continue;
                }

                final String value = structureAttribute.valueOf("value");
                if (StringUtils.isEmpty(nodeId)) {
                    logger.warn("value is empty for layout element, the attribute will be ignored: {}",
                            structureAttribute.asXML());
                    continue;
                }

                //Remove from the old attrs set as we've updated it
                final Map<String, String> oldAttrs = oldLayoutAttributes.get(nodeId);
                if (oldAttrs != null) {
                    oldAttrs.remove(name);
                }

                ssup.setLayoutAttribute(nodeId, name, value);

                // Remove the layout attribute element or DLM fails
                layoutElement.remove(structureAttribute);
            }

            //Purge orphaned entries
            for (final Entry<String, Map<String, String>> oldAttributeEntry : oldLayoutAttributes.entrySet()) {
                final String nodeId = oldAttributeEntry.getKey();
                for (final String name : oldAttributeEntry.getValue().keySet()) {
                    ssup.removeLayoutAttribute(nodeId, name);
                }
            }

            this.stylesheetUserPreferencesDao.storeStylesheetUserPreferences(ssup);
        }
    }

    @SuppressWarnings("unchecked")
    private final int addIdAttributesIfNecessary(org.dom4j.Element e, int nextId) {

        int idAfterThisOne = nextId; // default...
        final org.dom4j.Node idAttribute = e.selectSingleNode("@ID | @dlm:plfID");
        if (idAttribute == null) {
            if (logger.isDebugEnabled()) {
                logger.debug(
                        "No ID or dlm:plfID attribute for the following node (one will be generated and added):  element{}, name={}, fname={}",
                        e.getName(), e.valueOf("@name"), e.valueOf("@fname"));
            }

            // We need to add an ID to this node...
            char prefix;
            if (e.getName().equals("folder")) {
                prefix = 's';
            } else if (e.getName().equals("channel")) {
                prefix = 'n';
            } else if (e.getQName().getNamespacePrefix().equals("dlm")) {
                prefix = 'd';
            } else {
                throw new RuntimeException("Unrecognized element type:  " + e.getName());
            }

            final String origin = e.valueOf("@dlm:origin");
            // 'origin' may be null if the dlm:origin attribute is an 
            // empty string (which also shouldn't happen);  'origin' 
            // will be zero-length if dlm:origin is not defined...
            if (origin != null && origin.length() != 0) {
                // Add as dlm:plfID, if necessary...
                e.addAttribute("dlm:plfID", prefix + String.valueOf(nextId));
            } else {
                // Do the standard thing, if necessary...
                e.addAttribute("ID", prefix + String.valueOf(nextId));
            }

            ++idAfterThisOne;
        } else {
            final String id = idAttribute.getText();
            try {
                idAfterThisOne = Integer.parseInt(id.substring(1)) + 1;
            } catch (final NumberFormatException nfe) {
                logger.warn("Could not parse int value from id: {} The next layout id will be: {}", id,
                        idAfterThisOne, nfe);
            }
        }

        // Now check children...
        for (final Iterator<org.dom4j.Element> itr = (Iterator<org.dom4j.Element>) e
                .selectNodes("folder | channel | dlm:*").iterator(); itr.hasNext();) {
            final org.dom4j.Element child = itr.next();
            idAfterThisOne = this.addIdAttributesIfNecessary(child, idAfterThisOne);
        }

        return idAfterThisOne;

    }

    private final ThreadLocal<Cache<Tuple<String, String>, Document>> layoutCacheHolder = new ThreadLocal<Cache<Tuple<String, String>, Document>>();

    public void setLayoutImportExportCache(Cache<Tuple<String, String>, Document> layoutCache) {
        if (layoutCache == null) {
            layoutCacheHolder.remove();
        } else {
            this.layoutCacheHolder.set(layoutCache);
        }
    }

    public Cache<Tuple<String, String>, Document> getLayoutImportExportCache() {
        return layoutCacheHolder.get();
    }

    /**
     * Handles locking and identifying proper root and namespaces that used to
     * take place in super class.
     *
     * @param person
     * @param profile
     * @return
     * @
     */
    private Document _safeGetUserLayout(IPerson person, IUserProfile profile)

    {
        Document layoutDoc;
        Tuple<String, String> key = null;

        final Cache<Tuple<String, String>, Document> layoutCache = getLayoutImportExportCache();
        if (layoutCache != null) {
            key = new Tuple<String, String>(person.getUserName(), profile.getProfileFname());
            layoutDoc = layoutCache.getIfPresent(key);
            if (layoutDoc != null) {
                return (Document) layoutDoc.cloneNode(true);
            }
        }

        layoutDoc = super.getPersonalUserLayout(person, profile);
        Element layout = layoutDoc.getDocumentElement();
        layout.setAttribute(Constants.NS_DECL, Constants.NS_URI);

        if (layoutCache != null && key != null) {
            layoutCache.put(key, (Document) layoutDoc.cloneNode(true));
        }

        return layoutDoc;
    }

    /**
     * Returns the layout for a user. This method overrides the same
     * method in the superclass to return a composite layout for non
     * fragment owners and a regular layout for layout owners. A
     * composite layout is made up of layout pieces from potentially
     * multiple incorporated layouts. If no layouts are defined then
     * the composite layout will be the same as the user's personal
     * layout fragment or PLF, the one holding only those UI elements
     * that they own or incorporated elements that they have been
     * allowed to changed.
     **/
    private DistributedUserLayout _getUserLayout(IPerson person, IUserProfile profile)

    {
        final String userName = (String) person.getAttribute("username");
        final FragmentDefinition ownedFragment = this.fragmentUtils.getFragmentDefinitionByOwner(person);
        final boolean isLayoutOwnerDefault = this.isLayoutOwnerDefault(person);
        final Set<String> fragmentNames = new LinkedHashSet<String>();

        final Document ILF;
        final Document PLF = this.getPLF(person, profile);

        // If this user is an owner then ownedFragment will be non null. For
        // fragment owners and owners of any default layout from which a
        // fragment owners layout is copied there should not be any imported
        // distributed layouts. Instead, load their PLF, mark as an owned
        // if a fragment owner, and return.
        if (ownedFragment != null || isLayoutOwnerDefault) {
            ILF = (Document) PLF.cloneNode(true);
            final Element layoutNode = ILF.getDocumentElement();

            final Element ownerDocument = layoutNode.getOwnerDocument().getDocumentElement();
            final NodeList channelNodes = ownerDocument.getElementsByTagName("channel");
            for (int i = 0; i < channelNodes.getLength(); i++) {
                Element channelNode = (Element) channelNodes.item(i);
                final Node chanIdNode = channelNode.getAttributeNode("chanID");
                if (chanIdNode == null || MissingPortletDefinition.CHANNEL_ID.equals(chanIdNode.getNodeValue())) {
                    channelNode.getParentNode().removeChild(channelNode);
                }
            }

            if (ownedFragment != null) {
                fragmentNames.add(ownedFragment.getName());
                layoutNode.setAttributeNS(Constants.NS_URI, Constants.ATT_FRAGMENT_NAME, ownedFragment.getName());
                logger.debug("User '{}' is owner of '{}' fragment.", userName, ownedFragment.getName());
            } else if (isLayoutOwnerDefault) {
                layoutNode.setAttributeNS(Constants.NS_URI, Constants.ATT_IS_TEMPLATE_USER, "true");
                layoutNode.setAttributeNS(Constants.NS_URI, Constants.ATT_TEMPLATE_LOGIN_ID,
                        (String) person.getAttribute("username"));
            }
        } else {
            final Locale locale = profile.getLocaleManager().getLocales()[0];
            final List<FragmentDefinition> applicableFragmentDefinitions = this.fragmentUtils
                    .getFragmentDefinitionsApplicableToPerson(person);
            final List<Document> applicableLayouts = this.fragmentUtils
                    .getFragmentDefinitionUserViewLayouts(applicableFragmentDefinitions, locale);
            final IntegrationResult integrationResult = new IntegrationResult();
            ILF = this.createCompositeILF(person, PLF, applicableLayouts, integrationResult);
            // push optimizations made during merge back into db.
            if (integrationResult.isChangedPLF()) {
                if (logger.isDebugEnabled()) {
                    logger.debug("Saving PLF for {} due to changes during merge.",
                            person.getAttribute(IPerson.USERNAME));
                }
                super.setUserLayout(person, profile, PLF, false);
            }
            fragmentNames.addAll(this.fragmentUtils.getFragmentNames(applicableFragmentDefinitions));
        }
        return this.createDistributedUserLayout(person, profile, ILF, fragmentNames);
    }

    private Document getPLF(final IPerson person, final IUserProfile profile) {
        Document PLF = (Document) person.getAttribute(Constants.PLF);
        if (null == PLF) {
            PLF = this._safeGetUserLayout(person, profile);
            person.setAttribute(Constants.PLF, PLF);
        }
        if (logger.isDebugEnabled()) {
            logger.debug("PLF for {} immediately after loading\n{}", person.getAttribute(IPerson.USERNAME),
                    XmlUtilitiesImpl.toString(PLF));
        }
        return PLF;
    }

    /**
     * Creates a composite ILF (incorporated layouts fragment) by first using the applicable fragment layouts, then merging in the
     * PLF (personal layout fragment).
     */
    private Document createCompositeILF(final IPerson person, final Document PLF,
            final List<Document> applicableLayouts, final IntegrationResult integrationResult) {
        final Document ILF = ILFBuilder.constructILF(PLF, applicableLayouts, person);
        PLFIntegrator.mergePLFintoILF(PLF, ILF, integrationResult);
        if (logger.isDebugEnabled()) {
            logger.debug("PLF for {} after MERGING\n{}", person.getAttribute(IPerson.USERNAME),
                    XmlUtilitiesImpl.toString(PLF));
            logger.debug("ILF for {} after MERGING\n{}", person.getAttribute(IPerson.USERNAME),
                    XmlUtilitiesImpl.toString(ILF));
        }
        return ILF;
    }

    private DistributedUserLayout createDistributedUserLayout(final IPerson person, final IUserProfile profile,
            final Document ILF, final Set<String> fragmentNames) {
        final int structureStylesheetId = profile.getStructureStylesheetId();
        final IStylesheetUserPreferences distributedStructureStylesheetUserPreferences = this
                .loadDistributedStylesheetUserPreferences(person, profile, structureStylesheetId, fragmentNames);

        final int themeStylesheetId = profile.getThemeStylesheetId();
        final IStylesheetUserPreferences distributedThemeStylesheetUserPreferences = this
                .loadDistributedStylesheetUserPreferences(person, profile, themeStylesheetId, fragmentNames);

        return new DistributedUserLayout(ILF, fragmentNames, distributedStructureStylesheetUserPreferences,
                distributedThemeStylesheetUserPreferences);
    }

    /**
     * Convenience method for fragment activator to obtain raw layouts for
     * fragments during initialization.
     */
    public Document getFragmentLayout(IPerson person, IUserProfile profile)

    {
        return this._safeGetUserLayout(person, profile);
    }

    /**
     * Generates a new struct id for directive elements that dlm places in
     * the PLF version of the layout tree. These elements are atifacts of the
     * dlm storage model and used during merge but do not appear in the user's
     * composite view.
     */
    @Override
    public String getNextStructDirectiveId(IPerson person) {
        return super.getNextStructId(person, Constants.DIRECTIVE_PREFIX);
    }

    /**
       Replaces the layout Document stored on a fragment definition with a new
       version. This is called when a fragment owner updates their layout.
     */
    private void updateCachedLayout(Document layout, IUserProfile profile, FragmentDefinition fragment) {
        final Locale locale = profile.getLocaleManager().getLocales()[0];
        // need to make a copy that we can fragmentize
        layout = (Document) layout.cloneNode(true);

        // Fix later to handle multiple profiles
        final Element root = layout.getDocumentElement();
        final UserView userView = this.fragmentUtils.getUserView(fragment, locale);
        if (userView == null) {
            throw new IllegalStateException("No UserView found for fragment: " + fragment.getName());
        }

        root.setAttribute(Constants.ATT_ID, Constants.FRAGMENT_ID_USER_PREFIX + userView.getUserId()
                + Constants.FRAGMENT_ID_LAYOUT_PREFIX + "1");
        try {
            this.fragmentActivator.clearChacheForOwner(fragment.getOwnerId());
            this.fragmentUtils.getUserView(fragment, locale);
        } catch (final Exception e) {
            logger.error("An exception occurred attempting to update a layout.", e);
        }
    }

    /**
       Returns true is the user is the owner of a layout which is copied as the
       default for any fragment when first created.
    */
    private boolean isLayoutOwnerDefault(IPerson person) {
        final String userName = (String) person.getAttribute("username");

        final List<FragmentDefinition> definitions = this.fragmentUtils.getFragmentDefinitions();
        if (userName != null && definitions != null) {
            for (final FragmentDefinition fragmentDefinition : definitions) {
                if (fragmentDefinition.defaultLayoutOwnerID != null
                        && fragmentDefinition.defaultLayoutOwnerID.equals(userName)) {
                    return true;
                }
            }
        }
        final String globalDefault = PropertiesManager
                .getProperty("org.apereo.portal.layout.dlm.defaultLayoutOwner");
        if (globalDefault != null && globalDefault.equals(userName)) {
            return true;
        }

        if (!this.systemDefaultUserLoaded) {
            this.systemDefaultUserLoaded = true;
            try {
                this.systemDefaultUser = PropertiesManager.getProperty(TEMPLATE_USER_NAME);
            } catch (final RuntimeException re) {
                logger.error("Property '{}' not defined.", TEMPLATE_USER_NAME, re);
            }
            if (this.systemDefaultUser != null && this.systemDefaultUser.equals(userName)) {
                return true;
            }
        }

        return false;
    }

    @Override
    public boolean isFragmentOwner(IPerson person) {
        return this.fragmentUtils.getFragmentDefinitionByOwner(person) != null;
    }

    @Override
    public boolean isFragmentOwner(String username) {

        boolean rslt = false; // default

        final List<FragmentDefinition> definitions = this.fragmentUtils.getFragmentDefinitions();
        if (definitions != null) {
            for (final FragmentDefinition fragmentDefinition : definitions) {
                if (fragmentDefinition.getOwnerId().equals(username)) {
                    rslt = true;
                    break;
                }
            }
        }

        return rslt;

    }

    /**
       This method overrides the same method in the super class to persist
       only layout information stored in the user's person layout fragment
       or PLF. If this person is a layout owner then their changes are pushed
       into the appropriate layout fragment.
     */
    public void setUserLayout(IPerson person, IUserProfile profile, Document layoutXML, boolean channelsAdded)

    {
        this.setUserLayout(person, profile, layoutXML, channelsAdded, true);
    }

    /**
       This method overrides the same method in the super class to persist
       only layout information stored in the user's person layout fragment
       or PLF. If fragment cache update is requested then it checks to see if
       this person is a layout owner and if so then their changes are pushed
       into the appropriate layout fragment.
     */
    @Override
    public void setUserLayout(IPerson person, IUserProfile profile, Document layoutXML, boolean channelsAdded,
            boolean updateFragmentCache)

    {
        final Document plf = (Document) person.getAttribute(Constants.PLF);
        if (logger.isDebugEnabled()) {
            logger.debug("PLF for {}\n{}", person.getAttribute(IPerson.USERNAME), XmlUtilitiesImpl.toString(plf));
        }
        super.setUserLayout(person, profile, plf, channelsAdded);

        if (updateFragmentCache) {
            final FragmentDefinition fragment = this.fragmentUtils.getFragmentDefinitionByOwner(person);

            if (fragment != null) {
                this.updateCachedLayout(plf, profile, fragment);
            }
        }
    }

    @Override
    public FragmentChannelInfo getFragmentChannelInfo(String sId) {
        final FragmentNodeInfo node = this.getFragmentNodeInfo(sId);

        if (node != null && node instanceof FragmentChannelInfo) {
            return (FragmentChannelInfo) node;
        }
        return null;
    }

    @Override
    public FragmentNodeInfo getFragmentNodeInfo(String sId) {
        // grab local pointers to variables subject to change at any time
        final List<FragmentDefinition> fragments = this.fragmentUtils.getFragmentDefinitions();
        final Locale defaultLocale = LocaleManager.getPortalLocales()[0];

        final net.sf.ehcache.Element element = this.fragmentNodeInfoCache.get(sId);
        FragmentNodeInfo info = element != null ? (FragmentNodeInfo) element.getObjectValue() : null;

        if (info == null) {
            for (final FragmentDefinition fragmentDefinition : fragments) {
                final UserView userView = this.fragmentUtils.getUserView(fragmentDefinition, defaultLocale);
                if (userView == null) {
                    logger.warn(
                            "No UserView is present for fragment {} it will be skipped when fragment node information",
                            fragmentDefinition.getName());
                    continue;
                }

                final Element node = userView.getLayout().getElementById(sId);
                if (node != null) // found it
                {
                    if (node.getTagName().equals(Constants.ELM_CHANNEL)) {
                        info = new FragmentChannelInfo(node);
                    } else {
                        info = new FragmentNodeInfo(node);
                    }
                    this.fragmentNodeInfoCache.put(new net.sf.ehcache.Element(sId, info));
                    break;
                }
            }
        }
        return info;
    }

    @Override
    protected Element getStructure(Document doc, LayoutStructure ls) {
        Element structure = null;

        String type = ls.getType();

        if (ls.isChannel()) {
            final IPortletDefinition channelDef = this.portletDefinitionRegistry
                    .getPortletDefinition(String.valueOf(ls.getChanId()));
            if (channelDef != null && channelApproved(channelDef.getApprovalDate())) {
                structure = this.getElementForChannel(doc, channelPrefix + ls.getStructId(), channelDef,
                        ls.getLocale());
            } else {
                structure = this.getElementForChannel(doc, channelPrefix + ls.getStructId(),
                        MissingPortletDefinition.INSTANCE, null);
            }
        } else {
            // create folder objects including dlm new types in cp namespace
            if (type != null && type.startsWith(Constants.NS)) {
                structure = doc.createElementNS(Constants.NS_URI, type);
            } else {
                structure = doc.createElement("folder");
            }
            structure.setAttribute("name", ls.getName());
            structure.setAttribute("type", (type != null ? type : "regular"));
        }

        structure.setAttribute("hidden", (ls.isHidden() ? "true" : "false"));
        structure.setAttribute("immutable", (ls.isImmutable() ? "true" : "false"));
        structure.setAttribute("unremovable", (ls.isUnremovable() ? "true" : "false"));
        if (localeAware) {
            structure.setAttribute("locale", ls.getLocale()); // for i18n by Shoji
        }

        /*
         * Parameters from up_layout_param are loaded slightly differently for
         * folders and channels. For folders all parameters are added as attributes
         * of the Element. For channels only those parameters with names starting
         * with the dlm namespace Constants.NS are added as attributes to the Element.
         * Others are added as child parameter Elements.
         */
        if (ls.getParameters() != null) {
            for (final Iterator itr = ls.getParameters().iterator(); itr.hasNext();) {
                final StructureParameter sp = (StructureParameter) itr.next();
                String pName = sp.getName();

                if (!ls.isChannel()) { // Folder
                    if (pName.startsWith(Constants.NS)) {
                        structure.setAttributeNS(Constants.NS_URI, pName, sp.getValue());
                    } else {
                        structure.setAttribute(pName, sp.getValue());
                    }
                } else // Channel
                {
                    // if dealing with a dlm namespace param add as attribute
                    if (pName.startsWith(Constants.NS)) {
                        structure.setAttributeNS(Constants.NS_URI, pName, sp.getValue());
                        itr.remove();
                    } else {
                        /*
                         * do traditional override processing. some explanation is in
                         * order. The structure element was created by the
                         * ChannelDefinition and only contains parameter children if the
                         * definition had defined parameters. These are checked for each
                         * layout loaded parameter as found in LayoutStructure.parameters.
                         * If a name match is found then we need to see if overriding is
                         * allowed and if so we set the value on the child parameter
                         * element. At that point we are done with that version loaded
                         * from the layout so we remove it from the in-memory set of
                         * parameters that are being merged-in. Then, after all such have
                         * been checked against those added by the channel definition we
                         * add in any remaining as adhoc, unregulated parameters.
                         */
                        final NodeList nodeListParameters = structure.getElementsByTagName("parameter");
                        for (int j = 0; j < nodeListParameters.getLength(); j++) {
                            final Element parmElement = (Element) nodeListParameters.item(j);
                            final NamedNodeMap nm = parmElement.getAttributes();

                            final String nodeName = nm.getNamedItem("name").getNodeValue();
                            if (nodeName.equals(pName)) {
                                final Node override = nm.getNamedItem("override");
                                if (override != null && override.getNodeValue().equals("yes")) {
                                    final Node valueNode = nm.getNamedItem("value");
                                    valueNode.setNodeValue(sp.getValue());
                                }
                                itr.remove();
                                break; // found the corresponding one so skip the rest
                            }
                        }
                    }
                }
            }
            // For channels, add any remaining parameter elements loaded with the
            // layout as adhoc, unregulated, parameter children that can be overridden.
            if (ls.isChannel()) {
                for (final Iterator itr = ls.getParameters().iterator(); itr.hasNext();) {
                    final StructureParameter sp = (StructureParameter) itr.next();
                    final Element parameter = doc.createElement("parameter");
                    parameter.setAttribute("name", sp.getName());
                    parameter.setAttribute("value", sp.getValue());
                    parameter.setAttribute("override", "yes");
                    structure.appendChild(parameter);
                }
            }
        }
        // finish setting up elements based on loaded params
        final String origin = structure.getAttribute(Constants.ATT_ORIGIN);
        final String prefix = ls.isChannel() ? channelPrefix : folderPrefix;

        // if not null we are dealing with a node incorporated from another
        // layout and this node contains changes made by the user so handle
        // id swapping.
        if (!origin.equals("")) {
            structure.setAttributeNS(Constants.NS_URI, Constants.ATT_PLF_ID, prefix + ls.getStructId());
            structure.setAttribute("ID", origin);
        } else if (!ls.isChannel())
        // regular folder owned by this user, need to check if this is a
        // directive or ui element. If the latter then use traditional id
        // structure
        {
            if (type != null && type.startsWith(Constants.NS)) {
                structure.setAttribute("ID", Constants.DIRECTIVE_PREFIX + ls.getStructId());
            } else {
                structure.setAttribute("ID", folderPrefix + ls.getStructId());
            }
        } else {
            logger.debug("Adding identifier {}{}", folderPrefix, ls.getStructId());
            structure.setAttribute("ID", channelPrefix + ls.getStructId());
        }
        structure.setIdAttribute(Constants.ATT_ID, true);
        return structure;
    }

    @Override
    protected int saveStructure(Node node, PreparedStatement structStmt, PreparedStatement parmStmt)
            throws SQLException {
        if (node == null) { // No more
            return 0;
        }
        if (node.getNodeName().equals("parameter")) {
            //parameter, skip it and go on to the next node
            return this.saveStructure(node.getNextSibling(), structStmt, parmStmt);
        }
        if (!(node instanceof Element)) {
            return 0;
        }

        final Element structure = (Element) node;

        if (logger.isDebugEnabled()) {
            logger.debug("saveStructure XML content: {}", XmlUtilitiesImpl.toString(node));
        }

        // determine the struct_id for storing in the db. For incorporated nodes in
        // the plf their ID is a system-wide unique ID while their struct_id for
        // storing in the db is cached in a dlm:plfID attribute.
        int saveStructId = -1;
        final String plfID = structure.getAttribute(Constants.ATT_PLF_ID);

        if (!plfID.equals("")) {
            saveStructId = Integer.parseInt(plfID.substring(1));
        } else {
            final String id = structure.getAttribute("ID");
            saveStructId = Integer.parseInt(id.substring(1));
        }

        int nextStructId = 0;
        int childStructId = 0;
        int chanId = -1;
        IPortletDefinition portletDef = null;
        final boolean isChannel = node.getNodeName().equals("channel");

        if (isChannel) {
            chanId = Integer.parseInt(node.getAttributes().getNamedItem("chanID").getNodeValue());
            portletDef = this.portletDefinitionRegistry.getPortletDefinition(String.valueOf(chanId));
            if (portletDef == null) {
                //Portlet doesn't exist any more, drop the layout node
                return 0;
            }
        }

        if (node.hasChildNodes()) {
            childStructId = this.saveStructure(node.getFirstChild(), structStmt, parmStmt);
        }
        nextStructId = this.saveStructure(node.getNextSibling(), structStmt, parmStmt);
        structStmt.clearParameters();
        structStmt.setInt(1, saveStructId);
        structStmt.setInt(2, nextStructId);
        structStmt.setInt(3, childStructId);

        final String externalId = structure.getAttribute("external_id");
        if (externalId != null && externalId.trim().length() > 0) {
            final Integer eID = new Integer(externalId);
            structStmt.setInt(4, eID.intValue());
        } else {
            structStmt.setNull(4, java.sql.Types.NUMERIC);

        }
        if (isChannel) {
            structStmt.setInt(5, chanId);
            structStmt.setNull(6, java.sql.Types.VARCHAR);
        } else {
            structStmt.setNull(5, java.sql.Types.NUMERIC);
            structStmt.setString(6, structure.getAttribute("name"));
        }
        final String structType = structure.getAttribute("type");
        structStmt.setString(7, structType);
        structStmt.setString(8, RDBMServices.dbFlag(xmlBool(structure.getAttribute("hidden"))));
        structStmt.setString(9, RDBMServices.dbFlag(xmlBool(structure.getAttribute("immutable"))));
        structStmt.setString(10, RDBMServices.dbFlag(xmlBool(structure.getAttribute("unremovable"))));
        logger.debug(structStmt.toString());
        structStmt.executeUpdate();

        // code to persist extension attributes for dlm
        final NamedNodeMap attribs = node.getAttributes();
        for (int i = 0; i < attribs.getLength(); i++) {
            final Node attrib = attribs.item(i);
            final String name = attrib.getNodeName();

            if (name.startsWith(Constants.NS) && !name.equals(Constants.ATT_PLF_ID)
                    && !name.equals(Constants.ATT_FRAGMENT) && !name.equals(Constants.ATT_PRECEDENCE)) {
                // a cp extension attribute. Push into param table.
                parmStmt.clearParameters();
                parmStmt.setInt(1, saveStructId);
                parmStmt.setString(2, name);
                parmStmt.setString(3, attrib.getNodeValue());
                logger.debug(parmStmt.toString());
                parmStmt.executeUpdate();
            }
        }
        final NodeList parameters = node.getChildNodes();
        if (parameters != null && isChannel) {
            for (int i = 0; i < parameters.getLength(); i++) {
                if (parameters.item(i).getNodeName().equals("parameter")) {
                    final Element parmElement = (Element) parameters.item(i);
                    final NamedNodeMap nm = parmElement.getAttributes();
                    final String parmName = nm.getNamedItem("name").getNodeValue();
                    final String parmValue = nm.getNamedItem("value").getNodeValue();
                    final Node override = nm.getNamedItem("override");

                    // if no override specified then default to allowed
                    if (override != null && !override.getNodeValue().equals("yes")) {
                        // can't override
                    } else {
                        // override only for adhoc or if diff from chan def
                        final IPortletDefinitionParameter cp = portletDef.getParameter(parmName);
                        if (cp == null || !cp.getValue().equals(parmValue)) {
                            parmStmt.clearParameters();
                            parmStmt.setInt(1, saveStructId);
                            parmStmt.setString(2, parmName);
                            parmStmt.setString(3, parmValue);
                            logger.debug(parmStmt.toString());
                            parmStmt.executeUpdate();
                        }
                    }
                }
            }
        }
        return saveStructId;
    }

    public static Document getPLF(IPerson person) throws PortalException {
        try {
            return (Document) person.getAttribute(Constants.PLF);
        } catch (final Exception ex) {
            throw new PortalException(ex);
        }
    }

    private Element getElementForChannel(Document doc, String chanId, IPortletDefinition def, String locale) {
        final Element channel = doc.createElement("channel");

        // the ID attribute is the identifier for the Channel element
        channel.setAttribute("ID", chanId);
        channel.setIdAttribute("ID", true);

        channel.setAttribute("chanID", def.getPortletDefinitionId().getStringId());
        channel.setAttribute("timeout", String.valueOf(def.getTimeout()));
        if (locale != null) {
            channel.setAttribute("name", def.getName(locale));
            channel.setAttribute("title", def.getTitle(locale));
            channel.setAttribute("description", def.getDescription(locale));
            channel.setAttribute("locale", locale);
        } else {
            channel.setAttribute("name", def.getName());
            channel.setAttribute("title", def.getTitle());
            channel.setAttribute("description", def.getDescription());
        }
        channel.setAttribute("fname", def.getFName());

        // chanClassArg is so named to highlight that we are using the argument
        // to the method rather than the instance variable chanClass
        channel.setAttribute("typeID", String.valueOf(def.getType().getId()));

        for (final IPortletDefinitionParameter param : def.getParameters()) {
            final Element parameter = doc.createElement("parameter");
            parameter.setAttribute("name", param.getName());
            parameter.setAttribute("value", param.getValue());
            channel.appendChild(parameter);
        }

        return channel;

    }

    private interface FormOfLayoutCorruption {
        boolean detect(org.dom4j.Document layout);

        String getMessage();
    }

    private static final List<FormOfLayoutCorruption> KNOWN_FORMS_OF_LAYOUT_CORRUPTION = Collections
            .unmodifiableList(Arrays.asList(new FormOfLayoutCorruption[] {

                    // One <channel> element inside another
                    new FormOfLayoutCorruption() {
                        public boolean detect(org.dom4j.Document layoutDoc) {
                            return !layoutDoc.selectNodes("//channel/descendant::channel").isEmpty();
                        }

                        public String getMessage() {
                            return "one <channel> element inside another";
                        };
                    }

    }));

}