org.jasig.portal.layout.simple.RDBMUserLayoutStore.java Source code

Java tutorial

Introduction

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

import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.Hashtable;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Vector;

import javax.annotation.Resource;
import javax.sql.DataSource;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.jasig.portal.EntityIdentifier;
import org.jasig.portal.IUserProfile;
import org.jasig.portal.UserProfile;
import org.jasig.portal.i18n.ILocaleStore;
import org.jasig.portal.i18n.LocaleManager;
import org.jasig.portal.jpa.BasePortalJpaDao;
import org.jasig.portal.layout.IUserLayoutStore;
import org.jasig.portal.layout.LayoutStructure;
import org.jasig.portal.layout.dao.IStylesheetDescriptorDao;
import org.jasig.portal.portlet.registry.IPortletDefinitionRegistry;
import org.jasig.portal.rdbm.DatabaseMetaDataImpl;
import org.jasig.portal.rdbm.IDatabaseMetadata;
import org.jasig.portal.rdbm.IJoinQueryString;
import org.jasig.portal.security.IPerson;
import org.jasig.portal.security.IPersonManager;
import org.jasig.portal.security.ISecurityContext;
import org.jasig.portal.security.provider.PersonImpl;
import org.jasig.portal.spring.locator.CounterStoreLocator;
import org.jasig.portal.utils.DocumentFactory;
import org.jasig.portal.utils.ICounterStore;
import org.jasig.portal.utils.Tuple;
import org.jasig.portal.utils.threading.SingletonDoubleCheckedCreator;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.dao.DataAccessException;
import org.springframework.jdbc.core.ConnectionCallback;
import org.springframework.jdbc.core.JdbcOperations;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.support.SQLErrorCodeSQLExceptionTranslator;
import org.springframework.jdbc.support.SQLExceptionTranslator;
import org.springframework.transaction.PlatformTransactionManager;
import org.springframework.transaction.TransactionStatus;
import org.springframework.transaction.support.DefaultTransactionDefinition;
import org.springframework.transaction.support.TransactionCallback;
import org.springframework.transaction.support.TransactionOperations;
import org.springframework.transaction.support.TransactionTemplate;
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;

/**
 * SQL implementation for the 2.x relational database model.
 *
 * Prior to uPortal 2.5, this class existed in the org.jasig.portal package.  It was
 * moved to its present package to express that it is part of the
 * Simple Layout Manager implementation.
 *
 * @author George Lindholm
 * @version $Revision$ $Date$
 */
public abstract class RDBMUserLayoutStore implements IUserLayoutStore, InitializingBean {

    protected final Log log = LogFactory.getLog(getClass());
    private static String PROFILE_TABLE = "UP_USER_PROFILE";

    protected static final String DEFAULT_LAYOUT_FNAME = "default";

    //This class is instantiated ONCE so NO class variables can be used to keep state between calls
    protected static final String channelPrefix = "n";
    protected static final String folderPrefix = "s";

    protected TransactionOperations transactionOperations;
    protected TransactionOperations nextStructTransactionOperations;
    protected JdbcOperations jdbcOperations;

    private ILocaleStore localeStore;
    protected IDatabaseMetadata databaseMetadata;
    protected IPersonManager personManager;
    protected ICounterStore counterStore;
    protected IPortletDefinitionRegistry portletDefinitionRegistry;
    protected IStylesheetDescriptorDao stylesheetDescriptorDao;
    protected SQLExceptionTranslator exceptionTranslator;

    // I18n property
    protected static final boolean localeAware = LocaleManager.isLocaleAware();

    @Autowired
    public void setLocaleStore(ILocaleStore localeStore) {
        this.localeStore = localeStore;
    }

    @Autowired
    public void setStylesheetDescriptorDao(IStylesheetDescriptorDao stylesheetDescriptorDao) {
        this.stylesheetDescriptorDao = stylesheetDescriptorDao;
    }

    @Autowired
    public void setPlatformTransactionManager(
            @Qualifier(BasePortalJpaDao.PERSISTENCE_UNIT_NAME) PlatformTransactionManager platformTransactionManager) {
        this.transactionOperations = new TransactionTemplate(platformTransactionManager);

        final DefaultTransactionDefinition nextStructTransactionDefinition = new DefaultTransactionDefinition();
        nextStructTransactionDefinition
                .setPropagationBehavior(DefaultTransactionDefinition.PROPAGATION_REQUIRES_NEW);
        this.nextStructTransactionOperations = new TransactionTemplate(platformTransactionManager,
                nextStructTransactionDefinition);
    }

    @Resource(name = BasePortalJpaDao.PERSISTENCE_UNIT_NAME)
    public void setDataSource(DataSource dataSource) {
        this.jdbcOperations = new JdbcTemplate(dataSource);
        this.exceptionTranslator = new SQLErrorCodeSQLExceptionTranslator(dataSource);
    }

    @Autowired
    public void setDatabaseMetadata(IDatabaseMetadata databaseMetadata) {
        this.databaseMetadata = databaseMetadata;
    }

    @Autowired
    public void setPersonManager(IPersonManager personManager) {
        this.personManager = personManager;
    }

    @Autowired
    public void setCounterStore(ICounterStore counterStore) {
        this.counterStore = counterStore;
    }

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

    @Override
    public void afterPropertiesSet() throws Exception {
        if (this.databaseMetadata.supportsOuterJoins()) {
            final IJoinQueryString joinQuery = this.databaseMetadata.getJoinQuery();

            if (joinQuery instanceof DatabaseMetaDataImpl.JdbcDb) {
                joinQuery.addQuery("layout",
                        "{oj UP_LAYOUT_STRUCT ULS LEFT OUTER JOIN UP_LAYOUT_PARAM USP ON ULS.USER_ID = USP.USER_ID AND ULS.STRUCT_ID = USP.STRUCT_ID} WHERE");
                joinQuery.addQuery("ss_struct",
                        "{oj UP_SS_STRUCT USS LEFT OUTER JOIN UP_SS_STRUCT_PAR USP ON USS.SS_ID=USP.SS_ID} WHERE");
                joinQuery.addQuery("ss_theme",
                        "{oj UP_SS_THEME UTS LEFT OUTER JOIN UP_SS_THEME_PARM UTP ON UTS.SS_ID=UTP.SS_ID} WHERE");
            } else if (joinQuery instanceof DatabaseMetaDataImpl.PostgreSQLDb) {
                joinQuery.addQuery("layout",
                        "UP_LAYOUT_STRUCT ULS LEFT OUTER JOIN UP_LAYOUT_PARAM USP ON ULS.USER_ID = USP.USER_ID AND ULS.STRUCT_ID = USP.STRUCT_ID WHERE");
                joinQuery.addQuery("ss_struct",
                        "UP_SS_STRUCT USS LEFT OUTER JOIN UP_SS_STRUCT_PAR USP ON USS.SS_ID=USP.SS_ID WHERE");
                joinQuery.addQuery("ss_theme",
                        "UP_SS_THEME UTS LEFT OUTER JOIN UP_SS_THEME_PARM UTP ON UTS.SS_ID=UTP.SS_ID WHERE");
            } else if (joinQuery instanceof DatabaseMetaDataImpl.OracleDb) {
                joinQuery.addQuery("layout",
                        "UP_LAYOUT_STRUCT ULS, UP_LAYOUT_PARAM USP WHERE ULS.STRUCT_ID = USP.STRUCT_ID(+) AND ULS.USER_ID = USP.USER_ID(+) AND");
                joinQuery.addQuery("ss_struct",
                        "UP_SS_STRUCT USS, UP_SS_STRUCT_PAR USP WHERE USS.SS_ID=USP.SS_ID(+) AND");
                joinQuery.addQuery("ss_theme",
                        "UP_SS_THEME UTS, UP_SS_THEME_PARM UTP WHERE UTS.SS_ID=UTP.SS_ID(+) AND");
            } else {
                throw new RuntimeException("Unknown database driver");
            }
        }

    }

    private final SingletonDoubleCheckedCreator<IPerson> systemPersonCreator = new SingletonDoubleCheckedCreator<IPerson>() {
        protected IPerson createSingleton(Object... args) {
            // be sure we only do this once...
            // Load the "system" user id from the database
            final int systemUserId = jdbcOperations
                    .queryForInt("SELECT USER_ID FROM UP_USER WHERE USER_NAME = 'system'");
            log.info("Found user id " + systemUserId + " for the 'system' user.");
            return new SystemUser(systemUserId);
        }
    };

    private final IPerson getSystemUser() {
        return this.systemPersonCreator.get();
    }

    /**
     * Add a user profile
     * @param person
     * @param profile
     * @return userProfile
     * @exception Exception
     */
    public UserProfile addUserProfile(final IPerson person, final IUserProfile profile) {
        final int userId = person.getID();
        // generate an id for this profile

        return this.jdbcOperations.execute(new ConnectionCallback<UserProfile>() {
            @Override
            public UserProfile doInConnection(Connection con) throws SQLException, DataAccessException {
                String sQuery = null;
                PreparedStatement pstmt = con.prepareStatement("INSERT INTO UP_USER_PROFILE "
                        + "(USER_ID,PROFILE_ID,PROFILE_FNAME,PROFILE_NAME,STRUCTURE_SS_ID,THEME_SS_ID,"
                        + "DESCRIPTION, LAYOUT_ID) VALUES (?,?,?,?,?,?,?,?)");
                int profileId = getNextKey();
                pstmt.setInt(1, userId);
                pstmt.setInt(2, profileId);
                pstmt.setString(3, profile.getProfileFname());
                pstmt.setString(4, profile.getProfileName());
                pstmt.setInt(5, profile.getStructureStylesheetId());
                pstmt.setInt(6, profile.getThemeStylesheetId());
                pstmt.setString(7, profile.getProfileDescription());
                pstmt.setInt(8, profile.getLayoutId());
                sQuery = "INSERT INTO UP_USER_PROFILE (USER_ID,PROFILE_ID,PROFILE_FNAME,PROFILE_NAME,STRUCTURE_SS_ID,THEME_SS_ID,DESCRIPTION, LAYOUT_ID) VALUES ("
                        + userId + ",'" + profileId + ",'" + profile.getProfileFname() + "','"
                        + profile.getProfileName() + "'," + profile.getStructureStylesheetId() + ","
                        + profile.getThemeStylesheetId() + ",'" + profile.getProfileDescription() + "', "
                        + profile.getLayoutId() + ")";
                if (log.isDebugEnabled())
                    log.debug("RDBMUserLayoutStore::addUserProfile(): " + sQuery);
                try {
                    pstmt.executeUpdate();

                    UserProfile newProfile = new UserProfile();
                    newProfile.setProfileId(profileId);
                    newProfile.setLayoutId(profile.getLayoutId());
                    newProfile.setLocaleManager(profile.getLocaleManager());
                    newProfile.setProfileDescription(profile.getProfileDescription());
                    newProfile.setProfileFname(profile.getProfileFname());
                    newProfile.setProfileName(profile.getProfileName());
                    newProfile.setStructureStylesheetId(profile.getStructureStylesheetId());
                    newProfile.setSystemProfile(false);
                    newProfile.setThemeStylesheetId(profile.getThemeStylesheetId());

                    return newProfile;

                } finally {
                    pstmt.close();
                }
            }
        });
    }

    private int getNextKey() {
        return CounterStoreLocator.getCounterStore().getNextId(PROFILE_TABLE);
    }

    /**
     * Checks if a channel has been approved
     * @param approvedDate
     * @return boolean Channel is approved
     */
    protected static boolean channelApproved(java.util.Date approvedDate) {
        java.util.Date rightNow = new java.util.Date();
        return (approvedDate != null && rightNow.after(approvedDate));
    }

    /**
     * Create a layout
     * @param layoutStructure
     * @param doc
     * @param root
     * @param structId
     * @exception java.sql.SQLException
     */
    protected final void createLayout(HashMap layoutStructure, Document doc, Element root, int structId)
            throws java.sql.SQLException {
        while (structId != 0) {
            LayoutStructure ls = (LayoutStructure) layoutStructure.get(new Integer(structId));
            // replaced with call to method in containing class to allow overriding
            // by subclasses of RDBMUserLayoutStore.
            // Element structure = ls.getStructureDocument(doc);
            Element structure = getStructure(doc, ls);
            root.appendChild(structure);

            String id = structure.getAttribute("ID");
            if (id != null && !id.equals("")) {
                structure.setIdAttribute("ID", true);
            }

            createLayout(layoutStructure, doc, structure, ls.getChildId());
            structId = ls.getNextId();
        }
    }

    /**
     * convert true/false into Y/N for database
     * @param value to check
     * @result boolean
     */
    protected static final boolean xmlBool(String value) {
        return (value != null && value.equals("true") ? true : false);
    }

    public void deleteUserProfile(IPerson person, int profileId) {
        int userId = person.getID();
        deleteUserProfile(userId, profileId);
    }

    private void deleteUserProfile(final int userId, final int profileId) {
        this.jdbcOperations.execute(new ConnectionCallback<Object>() {
            @Override
            public Object doInConnection(Connection con) throws SQLException, DataAccessException {
                Statement stmt = con.createStatement();
                try {
                    String sQuery = "DELETE FROM UP_USER_PROFILE WHERE USER_ID=" + userId + " AND PROFILE_ID="
                            + Integer.toString(profileId);
                    if (log.isDebugEnabled())
                        log.debug("RDBMUserLayoutStore::deleteUserProfile() : " + sQuery);
                    stmt.executeUpdate(sQuery);

                    // remove profile mappings
                    sQuery = "DELETE FROM UP_USER_UA_MAP WHERE USER_ID=" + userId + " AND PROFILE_ID="
                            + Integer.toString(profileId);
                    if (log.isDebugEnabled())
                        log.debug("RDBMUserLayoutStore::deleteUserProfile() : " + sQuery);
                    stmt.executeUpdate(sQuery);

                    // remove parameter information
                    sQuery = "DELETE FROM UP_SS_USER_PARM WHERE USER_ID=" + userId + " AND PROFILE_ID="
                            + Integer.toString(profileId);
                    if (log.isDebugEnabled())
                        log.debug("RDBMUserLayoutStore::deleteUserProfile() : " + sQuery);
                    stmt.executeUpdate(sQuery);

                    sQuery = "DELETE FROM UP_SS_USER_ATTS WHERE USER_ID=" + userId + " AND PROFILE_ID="
                            + Integer.toString(profileId);
                    if (log.isDebugEnabled())
                        log.debug("RDBMUserLayoutStore::deleteUserProfile() : " + sQuery);
                    stmt.executeUpdate(sQuery);

                } finally {
                    stmt.close();
                }

                return null;
            }
        });
    }

    /**
     * Dump a document tree structure on stdout
     * @param node
     * @param indent
     */
    public static final void dumpDoc(Node node, String indent) {
        if (node == null) {
            return;
        }
        if (node instanceof Element) {
            System.err.print(indent + "element: tag=" + ((Element) node).getTagName() + " ");
        } else if (node instanceof Document) {
            System.err.print("document:");
        } else {
            System.err.print(indent + "node:");
        }
        System.err.println("name=" + node.getNodeName() + " value=" + node.getNodeValue());
        NamedNodeMap nm = node.getAttributes();
        if (nm != null) {
            for (int i = 0; i < nm.getLength(); i++) {
                System.err
                        .println(indent + " " + nm.item(i).getNodeName() + ": '" + nm.item(i).getNodeValue() + "'");
            }
            System.err.println(indent + "--");
        }
        if (node.hasChildNodes()) {
            dumpDoc(node.getFirstChild(), indent + "   ");
        }
        dumpDoc(node.getNextSibling(), indent);
    }

    /**
     * Return the next available channel structure id for a user
     * @param person
     * @return the next available channel structure id
     */
    public String generateNewChannelSubscribeId(IPerson person) {
        return getNextStructId(person, channelPrefix);
    }

    /**
     * Return the next available folder structure id for a user
     * @param person
     * @return a <code>String</code> that is the next free structure ID
     * @exception Exception
     */
    public String generateNewFolderId(IPerson person) {
        return getNextStructId(person, folderPrefix);
    }

    /**
     * Return the next available structure id for a user
     * @param person
     * @param prefix
     * @return next free structure ID
     * @exception Exception
     */
    protected String getNextStructId(final IPerson person, final String prefix) {
        final int userId = person.getID();
        return this.nextStructTransactionOperations.execute(new TransactionCallback<String>() {
            @Override
            public String doInTransaction(TransactionStatus status) {
                return jdbcOperations.execute(new ConnectionCallback<String>() {
                    @Override
                    public String doInConnection(Connection con) throws SQLException, DataAccessException {

                        Statement stmt = con.createStatement();
                        try {
                            String sQuery = "SELECT NEXT_STRUCT_ID FROM UP_USER WHERE USER_ID=" + userId;
                            if (log.isDebugEnabled())
                                log.debug("RDBMUserLayoutStore::getNextStructId(): " + sQuery);
                            ResultSet rs = stmt.executeQuery(sQuery);
                            int currentStructId;
                            try {
                                if (rs.next()) {
                                    currentStructId = rs.getInt(1);
                                } else {
                                    throw new SQLException("no rows returned for query [" + sQuery + "]");
                                }
                            } finally {
                                rs.close();
                            }
                            int nextStructId = currentStructId + 1;
                            String sUpdate = "UPDATE UP_USER SET NEXT_STRUCT_ID=" + nextStructId + " WHERE USER_ID="
                                    + userId + " AND NEXT_STRUCT_ID=" + currentStructId;
                            if (log.isDebugEnabled())
                                log.debug("RDBMUserLayoutStore::getNextStructId(): " + sUpdate);
                            stmt.executeUpdate(sUpdate);
                            return prefix + nextStructId;
                        } finally {
                            stmt.close();
                        }
                    }
                });
            }
        });
    }

    /**
     * Return the Structure ID tag
     * @param  structId
     * @param  chanId
     * @return ID tag
     */
    protected String getStructId(int structId, int chanId) {
        if (chanId == 0) {
            return folderPrefix + structId;
        } else {
            return channelPrefix + structId;
        }
    }

    // private helper modules that retreive information from the DOM structure of the description files
    private String getName(Document descr) {
        NodeList names = descr.getElementsByTagName("name");
        Node name = null;
        for (int i = names.getLength() - 1; i >= 0; i--) {
            name = names.item(i);
            if (name.getParentNode().getNodeName().equals("stylesheetdescription"))
                break;
            else
                name = null;
        }
        if (name != null) {
            return this.getTextChildNodeValue(name);
        } else {
            if (log.isDebugEnabled())
                log.debug(
                        "RDBMUserLayoutStore::getName() : no \"name\" element was found under the \"stylesheetdescription\" node!");
            return null;
        }
    }

    private String getRootElementTextValue(Document descr, String elementName) {
        NodeList names = descr.getElementsByTagName(elementName);
        Node name = null;
        for (int i = names.getLength() - 1; i >= 0; i--) {
            name = names.item(i);

            if (name.getParentNode().getNodeName().equals("stylesheetdescription"))
                break;
            else
                name = null;
        }
        if (name != null) {
            return this.getTextChildNodeValue(name);
        } else {
            if (log.isDebugEnabled())
                log.debug("RDBMUserLayoutStore::getRootElementTextValue() : no \"" + elementName
                        + "\" element was found under the \"stylesheetdescription\" node!");
            return null;
        }
    }

    private String getDescription(Document descr) {
        NodeList descriptions = descr.getElementsByTagName("description");
        Node description = null;
        for (int i = descriptions.getLength() - 1; i >= 0; i--) {
            description = descriptions.item(i);
            if (description.getParentNode().getNodeName().equals("stylesheetdescription"))
                break;
            else
                description = null;
        }
        if (description != null) {
            return this.getTextChildNodeValue(description);
        } else {
            if (log.isDebugEnabled())
                log.debug(
                        "RDBMUserLayoutStore::getDescription() : no \"description\" element was found under the \"stylesheetdescription\" node!");
            return null;
        }
    }

    private Vector getVectorOfSimpleTextElementValues(Document descr, String elementName) {
        Vector v = new Vector();
        // find "stylesheetdescription" node, take the first one
        Element stylesheetdescriptionElement = (Element) (descr.getElementsByTagName("stylesheetdescription"))
                .item(0);
        if (stylesheetdescriptionElement == null) {
            log.error("Could not obtain <stylesheetdescription> element");
            return null;
        }
        NodeList elements = stylesheetdescriptionElement.getElementsByTagName(elementName);
        for (int i = elements.getLength() - 1; i >= 0; i--) {
            v.add(this.getTextChildNodeValue(elements.item(i)));
        }
        return v;
    }

    private String getTextChildNodeValue(Node node) {
        if (node == null)
            return null;
        NodeList children = node.getChildNodes();
        for (int i = children.getLength() - 1; i >= 0; i--) {
            Node child = children.item(i);
            if (child.getNodeType() == Node.TEXT_NODE)
                return child.getNodeValue();
        }
        return null;
    }

    /**
     *   UserPreferences
     */
    private String getUserBrowserMapping(final IPerson person, final String userAgentArg) {
        final int userId = person.getID();
        return jdbcOperations.execute(new ConnectionCallback<String>() {
            @Override
            public String doInConnection(Connection con) throws SQLException, DataAccessException {
                final String userAgent;
                if (userAgentArg.length() > 255) {
                    userAgent = userAgentArg.substring(0, 254);
                    log.debug("userAgent trimmed to 255 characters. userAgent: " + userAgentArg);
                } else {
                    userAgent = userAgentArg;
                }

                String sQuery = "SELECT PROFILE_FNAME " + "FROM UP_USER_UA_MAP LEFT JOIN UP_USER_PROFILE ON "
                        + "UP_USER_UA_MAP.PROFILE_ID=UP_USER_PROFILE.PROFILE_ID WHERE UP_USER_UA_MAP.USER_ID=? AND USER_AGENT=?";
                PreparedStatement pstmt = con.prepareStatement(sQuery);

                try {
                    pstmt.setInt(1, userId);
                    pstmt.setString(2, userAgent);

                    if (log.isDebugEnabled())
                        log.debug("RDBMUserLayoutStore::getUserBrowserMapping(): '" + sQuery + "' userId: " + userId
                                + " userAgent: " + userAgent);
                    ResultSet rs = pstmt.executeQuery();
                    try {
                        if (rs.next()) {
                            return rs.getString("PROFILE_FNAME");
                        }
                    } finally {
                        rs.close();
                    }
                } finally {
                    pstmt.close();
                }

                return null;
            }
        });
    }

    protected Document getPersonalUserLayout(final IPerson person, final IUserProfile profile) {
        final LocaleManager localeManager = profile.getLocaleManager();

        return jdbcOperations.execute(new ConnectionCallback<Document>() {
            @Override
            public Document doInConnection(Connection con) throws SQLException, DataAccessException {

                ResultSet rs;
                int userId = person.getID();
                final int realUserId = userId;
                Document doc = DocumentFactory.getThreadDocument();
                Element root = doc.createElement("layout");
                final Statement stmt = con.createStatement();
                // A separate statement is needed so as not to interfere with ResultSet
                // of statements used for queries
                Statement insertStmt = con.createStatement();
                try {
                    long startTime = System.currentTimeMillis();
                    // eventually, we need to fix template layout implementations so you can just do this:
                    //        int layoutId=profile.getLayoutId();
                    // but for now:
                    int layoutId = getLayoutID(userId, profile.getProfileId());

                    if (layoutId == 0) { // First time, grab the default layout for this user
                        final Tuple<Integer, Integer> userLayoutIds = transactionOperations
                                .execute(new TransactionCallback<Tuple<Integer, Integer>>() {
                                    @Override
                                    public Tuple<Integer, Integer> doInTransaction(TransactionStatus status) {
                                        return jdbcOperations
                                                .execute(new ConnectionCallback<Tuple<Integer, Integer>>() {
                                                    @Override
                                                    public Tuple<Integer, Integer> doInConnection(Connection con)
                                                            throws SQLException, DataAccessException {

                                                        int newLayoutId;
                                                        int newUserId;

                                                        String sQuery = "SELECT USER_DFLT_USR_ID, USER_DFLT_LAY_ID FROM UP_USER WHERE USER_ID="
                                                                + realUserId;
                                                        if (log.isDebugEnabled())
                                                            log.debug("RDBMUserLayoutStore::getUserLayout(): "
                                                                    + sQuery);
                                                        ResultSet rs = stmt.executeQuery(sQuery);
                                                        try {
                                                            boolean hasRow = rs.next();
                                                            newUserId = rs.getInt(1);
                                                            newLayoutId = rs.getInt(2);
                                                        } finally {
                                                            rs.close();
                                                        }

                                                        // Make sure the next struct id is set in case the user adds a channel
                                                        sQuery = "SELECT NEXT_STRUCT_ID FROM UP_USER WHERE USER_ID="
                                                                + newUserId;
                                                        if (log.isDebugEnabled())
                                                            log.debug("RDBMUserLayoutStore::getUserLayout(): "
                                                                    + sQuery);
                                                        int nextStructId;
                                                        rs = stmt.executeQuery(sQuery);
                                                        try {
                                                            boolean hasRow = rs.next();
                                                            nextStructId = rs.getInt(1);
                                                        } finally {
                                                            rs.close();
                                                        }

                                                        int realNextStructId = 0;

                                                        if (realUserId != newUserId) {
                                                            // But never make the existing value SMALLER, change it only to make it LARGER
                                                            // (so, get existing value)
                                                            sQuery = "SELECT NEXT_STRUCT_ID FROM UP_USER WHERE USER_ID="
                                                                    + realUserId;
                                                            if (log.isDebugEnabled())
                                                                log.debug("RDBMUserLayoutStore::getUserLayout(): "
                                                                        + sQuery);
                                                            rs = stmt.executeQuery(sQuery);
                                                            try {
                                                                boolean hasRow = rs.next();
                                                                realNextStructId = rs.getInt(1);
                                                            } finally {
                                                                rs.close();
                                                            }
                                                        }

                                                        if (nextStructId > realNextStructId) {
                                                            sQuery = "UPDATE UP_USER SET NEXT_STRUCT_ID="
                                                                    + nextStructId + " WHERE USER_ID=" + realUserId;
                                                            if (log.isDebugEnabled())
                                                                log.debug("RDBMUserLayoutStore::getUserLayout(): "
                                                                        + sQuery);
                                                            stmt.executeUpdate(sQuery);
                                                        }

                                                        return new Tuple<Integer, Integer>(newUserId, newLayoutId);

                                                    }
                                                });
                                    }
                                });

                        userId = userLayoutIds.first;
                        layoutId = userLayoutIds.second;
                    }

                    int firstStructId = -1;

                    //Flags to enable a default layout lookup if it's needed
                    boolean foundLayout = false;
                    boolean triedDefault = false;

                    //This loop is used to ensure a layout is found for a user. It tries
                    //looking up the layout for the current userID. If one isn't found
                    //the userID is replaced with the template user ID for this user and
                    //the layout is searched for again. This loop should only ever loop once.
                    do {
                        String sQuery = "SELECT INIT_STRUCT_ID FROM UP_USER_LAYOUT WHERE USER_ID=" + userId
                                + " AND LAYOUT_ID = " + layoutId;
                        if (log.isDebugEnabled())
                            log.debug("RDBMUserLayoutStore::getUserLayout(): " + sQuery);
                        rs = stmt.executeQuery(sQuery);
                        try {
                            if (rs.next()) {
                                firstStructId = rs.getInt(1);
                            } else {
                                throw new RuntimeException(
                                        "RDBMUserLayoutStore::getUserLayout(): No INIT_STRUCT_ID in UP_USER_LAYOUT for USER_ID: "
                                                + userId + " and LAYOUT_ID: " + layoutId);
                            }
                        } finally {
                            rs.close();
                        }

                        String sql;
                        if (localeAware) {
                            // This needs to be changed to get the localized strings
                            sql = "SELECT ULS.STRUCT_ID,ULS.NEXT_STRUCT_ID,ULS.CHLD_STRUCT_ID,ULS.CHAN_ID,ULS.NAME,ULS.TYPE,ULS.HIDDEN,"
                                    + "ULS.UNREMOVABLE,ULS.IMMUTABLE";
                        } else {
                            sql = "SELECT ULS.STRUCT_ID,ULS.NEXT_STRUCT_ID,ULS.CHLD_STRUCT_ID,ULS.CHAN_ID,ULS.NAME,ULS.TYPE,ULS.HIDDEN,"
                                    + "ULS.UNREMOVABLE,ULS.IMMUTABLE";
                        }
                        if (databaseMetadata.supportsOuterJoins()) {
                            sql += ",USP.STRUCT_PARM_NM,USP.STRUCT_PARM_VAL FROM "
                                    + databaseMetadata.getJoinQuery().getQuery("layout");
                        } else {
                            sql += " FROM UP_LAYOUT_STRUCT ULS WHERE ";
                        }
                        sql += " ULS.USER_ID=" + userId + " AND ULS.LAYOUT_ID=" + layoutId
                                + " ORDER BY ULS.STRUCT_ID";
                        if (log.isDebugEnabled())
                            log.debug("RDBMUserLayoutStore::getUserLayout(): " + sql);
                        rs = stmt.executeQuery(sql);

                        //check for rows in the result set
                        foundLayout = rs.next();

                        if (!foundLayout && !triedDefault && userId == realUserId) {
                            //If we didn't find any rows and we haven't tried the default user yet
                            triedDefault = true;
                            rs.close();

                            //Get the default user ID and layout ID
                            sQuery = "SELECT USER_DFLT_USR_ID, USER_DFLT_LAY_ID FROM UP_USER WHERE USER_ID="
                                    + userId;
                            if (log.isDebugEnabled())
                                log.debug("RDBMUserLayoutStore::getUserLayout(): " + sQuery);
                            rs = stmt.executeQuery(sQuery);
                            try {
                                rs.next();
                                userId = rs.getInt(1);
                                layoutId = rs.getInt(2);
                            } finally {
                                rs.close();
                            }
                        } else {
                            //We tried the default or actually found a layout
                            break;
                        }
                    } while (!foundLayout);

                    HashMap layoutStructure = new HashMap();
                    StringBuffer structChanIds = new StringBuffer();

                    try {
                        int lastStructId = 0;
                        LayoutStructure ls = null;
                        String sepChar = "";
                        if (foundLayout) {
                            int structId = rs.getInt(1);
                            // Result Set returns 0 by default if structId was null
                            // Except if you are using poolman 2.0.4 in which case you get -1 back
                            if (rs.wasNull()) {
                                structId = 0;
                            }
                            readLayout: while (true) {

                                int nextId = rs.getInt(2);
                                if (rs.wasNull()) {
                                    nextId = 0;
                                }
                                int childId = rs.getInt(3);
                                if (rs.wasNull()) {
                                    childId = 0;
                                }
                                int chanId = rs.getInt(4);
                                if (rs.wasNull()) {
                                    chanId = 0;
                                }
                                String temp5 = rs.getString(5); // Some JDBC drivers require columns accessed in order
                                String temp6 = rs.getString(6); // Access 5 and 6 now, save till needed.

                                // uPortal i18n
                                int name_index, value_index;
                                if (localeAware) {
                                    Locale[] locales = localeManager.getLocales();
                                    String locale = locales[0].toString();
                                    ls = new LayoutStructure(structId, nextId, childId, chanId, rs.getString(7),
                                            rs.getString(8), rs.getString(9), locale);
                                    name_index = 10;
                                    value_index = 11;
                                } else {
                                    ls = new LayoutStructure(structId, nextId, childId, chanId, rs.getString(7),
                                            rs.getString(8), rs.getString(9));
                                    name_index = 10;
                                    value_index = 11;
                                }
                                layoutStructure.put(new Integer(structId), ls);
                                lastStructId = structId;
                                if (!ls.isChannel()) {
                                    ls.addFolderData(temp5, temp6); // Plug in saved column values
                                }
                                if (databaseMetadata.supportsOuterJoins()) {
                                    do {
                                        String name = rs.getString(name_index);
                                        String value = rs.getString(value_index); // Oracle JDBC requires us to do this for longs
                                        if (name != null) { // may not be there because of the join
                                            ls.addParameter(name, value);
                                        }
                                        if (!rs.next()) {
                                            break readLayout;
                                        }
                                        structId = rs.getInt(1);
                                        if (rs.wasNull()) {
                                            structId = 0;
                                        }
                                    } while (structId == lastStructId);
                                } else { // Do second SELECT later on for structure parameters
                                    if (ls.isChannel()) {
                                        structChanIds.append(sepChar + ls.getChanId());
                                        sepChar = ",";
                                    }
                                    if (rs.next()) {
                                        structId = rs.getInt(1);
                                        if (rs.wasNull()) {
                                            structId = 0;
                                        }
                                    } else {
                                        break readLayout;
                                    }
                                }
                            } // while
                        }
                    } finally {
                        rs.close();
                    }

                    if (!databaseMetadata.supportsOuterJoins() && structChanIds.length() > 0) { // Pick up structure parameters
                        // first, get the struct ids for the channels
                        String sql = "SELECT STRUCT_ID FROM UP_LAYOUT_STRUCT WHERE USER_ID=" + userId
                                + " AND LAYOUT_ID=" + layoutId + " AND CHAN_ID IN (" + structChanIds.toString()
                                + ") ORDER BY STRUCT_ID";

                        if (log.isDebugEnabled())
                            log.debug("RDBMUserLayoutStore::getUserLayout(): " + sql);
                        StringBuffer structIdsSB = new StringBuffer("");
                        String sep = "";
                        rs = stmt.executeQuery(sql);
                        try {
                            // use the results to build a correct list of struct ids to look for
                            while (rs.next()) {
                                structIdsSB.append(sep + rs.getString(1));
                                sep = ",";
                            } // while
                        } finally {
                            rs.close();
                        } // be a good doobie

                        sql = "SELECT STRUCT_ID, STRUCT_PARM_NM,STRUCT_PARM_VAL FROM UP_LAYOUT_PARAM WHERE USER_ID="
                                + userId + " AND LAYOUT_ID=" + layoutId + " AND STRUCT_ID IN ("
                                + structIdsSB.toString() + ") ORDER BY STRUCT_ID";
                        if (log.isDebugEnabled())
                            log.debug("RDBMUserLayoutStore::getUserLayout(): " + sql);
                        rs = stmt.executeQuery(sql);
                        try {
                            if (rs.next()) {
                                int structId = rs.getInt(1);
                                readParm: while (true) {
                                    LayoutStructure ls = (LayoutStructure) layoutStructure
                                            .get(new Integer(structId));
                                    int lastStructId = structId;
                                    do {
                                        ls.addParameter(rs.getString(2), rs.getString(3));
                                        if (!rs.next()) {
                                            break readParm;
                                        }
                                    } while ((structId = rs.getInt(1)) == lastStructId);
                                }
                            }
                        } finally {
                            rs.close();
                        }
                    }

                    if (layoutStructure.size() > 0) { // We have a layout to work with
                        createLayout(layoutStructure, doc, root, firstStructId);
                        layoutStructure.clear();

                        if (log.isDebugEnabled()) {
                            long stopTime = System.currentTimeMillis();
                            log.debug("RDBMUserLayoutStore::getUserLayout(): Layout document for user " + userId
                                    + " took " + (stopTime - startTime) + " milliseconds to create");
                        }

                        doc.appendChild(root);
                    }
                } finally {
                    stmt.close();
                    insertStmt.close();
                }
                return doc;
            }
        });
    }

    public IUserProfile getUserProfileById(final IPerson person, final int profileId) {
        final int userId = person.getID();
        return jdbcOperations.execute(new ConnectionCallback<IUserProfile>() {
            @Override
            public IUserProfile doInConnection(Connection con) throws SQLException, DataAccessException {

                Statement stmt = con.createStatement();
                try {
                    String sQuery = "SELECT USER_ID, PROFILE_ID, PROFILE_FNAME, PROFILE_NAME, DESCRIPTION, LAYOUT_ID, STRUCTURE_SS_ID, THEME_SS_ID FROM UP_USER_PROFILE WHERE USER_ID="
                            + userId + " AND PROFILE_ID=" + profileId;
                    if (log.isDebugEnabled())
                        log.debug("RDBMUserLayoutStore::getUserProfileById(): " + sQuery);
                    ResultSet rs = stmt.executeQuery(sQuery);
                    try {
                        if (rs.next()) {
                            String temp2 = rs.getString(3);
                            String temp3 = rs.getString(4);
                            String temp4 = rs.getString(5);
                            int layoutId = rs.getInt(6);
                            if (rs.wasNull()) {
                                layoutId = 0;
                            }
                            int structSsId = rs.getInt(7);
                            if (rs.wasNull()) {
                                // This is probably a data issue and probably an export operation;  defer to the system user...
                                if (!person.equals(getSystemUser())) {
                                    structSsId = getSystemProfileByFname(temp2).getStructureStylesheetId();
                                } else {
                                    String msg = "The system user profile has no structure stylesheet Id.";
                                    throw new IllegalStateException(msg);
                                }
                            }
                            int themeSsId = rs.getInt(8);
                            if (rs.wasNull()) {
                                // This is probably a data issue and probably an export operation;  defer to the system user...
                                if (!person.equals(getSystemUser())) {
                                    themeSsId = getSystemProfileByFname(temp2).getThemeStylesheetId();
                                } else {
                                    String msg = "The system user profile has no theme stylesheet Id.";
                                    throw new IllegalStateException(msg);
                                }
                            }
                            IUserProfile userProfile = new UserProfile(profileId, temp2, temp3, temp4, layoutId,
                                    structSsId, themeSsId);
                            final Locale[] userLocales = localeStore.getUserLocales(person);
                            userProfile.setLocaleManager(new LocaleManager(person, userLocales));
                            return userProfile;
                        } else {
                            throw new RuntimeException(
                                    "Unable to find User Profile for user " + userId + " and profile " + profileId);
                        }
                    } finally {
                        rs.close();
                    }
                } finally {
                    stmt.close();
                }

            }
        });
    }

    private final ThreadLocal<Cache<Tuple<String, String>, UserProfile>> profileCacheHolder = new ThreadLocal<Cache<Tuple<String, String>, UserProfile>>();

    /**
     * Cache used during import/export operations
     */
    public void setProfileImportExportCache(Cache<Tuple<String, String>, UserProfile> profileCache) {
        if (profileCache == null) {
            this.profileCacheHolder.remove();
        } else {
            this.profileCacheHolder.set(profileCache);
        }
    }

    private Cache<Tuple<String, String>, UserProfile> getProfileImportExportCache() {
        return this.profileCacheHolder.get();
    }

    public UserProfile getUserProfileByFname(final IPerson person, final String profileFname) {
        Tuple<String, String> key = null;
        final Cache<Tuple<String, String>, UserProfile> profileCache = getProfileImportExportCache();
        if (profileCache != null) {
            key = new Tuple<String, String>(person.getUserName(), profileFname);
            final UserProfile profile = profileCache.getIfPresent(key);
            if (profile != null) {
                return profile;
            }
        }

        log.debug("Getting profile " + profileFname + " for user " + person.getID());
        final int userId = person.getID();
        final UserProfile userProfile = jdbcOperations.execute(new ConnectionCallback<UserProfile>() {
            @Override
            public UserProfile doInConnection(Connection con) throws SQLException, DataAccessException {

                String query = "SELECT USER_ID, PROFILE_ID, PROFILE_NAME, DESCRIPTION, "
                        + "LAYOUT_ID, STRUCTURE_SS_ID, THEME_SS_ID FROM UP_USER_PROFILE WHERE "
                        + "USER_ID=? AND PROFILE_FNAME=?";
                PreparedStatement pstmt = con.prepareStatement(query);
                pstmt.setInt(1, userId);
                pstmt.setString(2, profileFname);
                try {
                    if (log.isDebugEnabled())
                        log.debug("RDBMUserLayoutStore::getUserProfileByFname(): " + query + " userId: " + userId
                                + " profileFname: " + profileFname);
                    ResultSet rs = pstmt.executeQuery();
                    try {
                        if (rs.next()) {
                            int profileId = rs.getInt(2);
                            String profileName = rs.getString(3);
                            String profileDesc = rs.getString(4);
                            int layoutId = rs.getInt(5);
                            if (rs.wasNull()) {
                                layoutId = 0;
                            }
                            int structSsId = rs.getInt(6);
                            if (rs.wasNull()) {
                                // This is probably a data issue and probably an export operation;  defer to the system user...
                                if (!person.equals(getSystemUser())) {
                                    structSsId = getSystemProfileByFname(profileFname).getStructureStylesheetId();
                                } else {
                                    String msg = "The system user profile has no structure stylesheet Id.";
                                    throw new IllegalStateException(msg);
                                }
                            }
                            int themeSsId = rs.getInt(7);
                            if (rs.wasNull()) {
                                // This is probably a data issue and probably an export operation;  defer to the system user...
                                if (!person.equals(getSystemUser())) {
                                    themeSsId = getSystemProfileByFname(profileFname).getThemeStylesheetId();
                                } else {
                                    String msg = "The system user profile has no theme stylesheet Id.";
                                    throw new IllegalStateException(msg);
                                }
                            }
                            UserProfile userProfile = new UserProfile(profileId, profileFname, profileName,
                                    profileDesc, layoutId, structSsId, themeSsId);
                            final Locale[] userLocales = localeStore.getUserLocales(person);
                            userProfile.setLocaleManager(new LocaleManager(person, userLocales));
                            return userProfile;
                        }

                        /* Try to copy the template profile. */
                        log.debug("Copying template profile " + profileFname + " to user " + person.getID());
                        rs.close();
                        pstmt.close();
                        pstmt = con.prepareStatement("SELECT USER_DFLT_USR_ID FROM UP_USER WHERE USER_ID=?");
                        pstmt.setInt(1, person.getID());
                        rs = pstmt.executeQuery();
                        if (rs.next()) {
                            int defaultProfileUser = rs.getInt(1);
                            if (rs.wasNull()) {
                                throw new RuntimeException(
                                        "Need to clone the '" + profileFname + "' profile from template user for "
                                                + person + " but they have no template user");
                            }

                            IPerson defaultProfilePerson = new PersonImpl();
                            defaultProfilePerson.setID(defaultProfileUser);
                            if (defaultProfilePerson.getID() != person.getID()) {
                                UserProfile templateProfile = getUserProfileByFname(defaultProfilePerson,
                                        profileFname);
                                if (templateProfile != null) {
                                    UserProfile newUserProfile = new UserProfile(templateProfile);
                                    final Locale[] userLocales = localeStore.getUserLocales(person);
                                    newUserProfile.setLayoutId(0);
                                    newUserProfile = addUserProfile(person, newUserProfile);

                                    newUserProfile.setLocaleManager(new LocaleManager(person, userLocales));
                                    return newUserProfile;
                                }
                            }
                        }

                        throw new RuntimeException("Unable to find User Profile for userId " + userId
                                + " and profile " + profileFname);
                    } finally {
                        rs.close();
                    }
                } finally {
                    pstmt.close();
                }

            }
        });
        if (profileCache != null && key != null) {
            profileCache.put(key, userProfile);
        }
        return userProfile;
    }

    public Hashtable getUserProfileList(final IPerson person) {
        final int userId = person.getID();

        return jdbcOperations.execute(new ConnectionCallback<Hashtable>() {
            @Override
            public Hashtable doInConnection(Connection con) throws SQLException, DataAccessException {

                Hashtable<Integer, UserProfile> pv = new Hashtable<Integer, UserProfile>();
                Statement stmt = con.createStatement();
                try {
                    String sQuery = "SELECT USER_ID, PROFILE_ID, PROFILE_FNAME, PROFILE_NAME, DESCRIPTION, LAYOUT_ID, STRUCTURE_SS_ID, THEME_SS_ID FROM UP_USER_PROFILE WHERE USER_ID="
                            + userId;
                    if (log.isDebugEnabled())
                        log.debug("RDBMUserLayoutStore::getUserProfileList(): " + sQuery);
                    ResultSet rs = stmt.executeQuery(sQuery);
                    try {
                        while (rs.next()) {
                            int layoutId = rs.getInt(6);
                            if (rs.wasNull()) {
                                layoutId = 0;
                            }
                            int structSsId = rs.getInt(7);
                            if (rs.wasNull()) {
                                structSsId = 0;
                            }
                            int themeSsId = rs.getInt(8);
                            if (rs.wasNull()) {
                                themeSsId = 0;
                            }

                            UserProfile upl = new UserProfile(rs.getInt(2), rs.getString(3), rs.getString(4),
                                    rs.getString(5), layoutId, structSsId, themeSsId);
                            pv.put(new Integer(upl.getProfileId()), upl);
                        }
                    } finally {
                        rs.close();
                    }
                } finally {
                    stmt.close();
                }
                return pv;

            }
        });
    }

    protected abstract Element getStructure(Document doc, LayoutStructure ls);

    protected abstract int saveStructure(Node node, PreparedStatement structStmt, PreparedStatement parmStmt)
            throws SQLException;

    public void setUserBrowserMapping(final IPerson person, final String userAgentArg, final int profileId) {
        final int userId = person.getID();

        this.transactionOperations.execute(new TransactionCallback<Object>() {
            @Override
            public Object doInTransaction(TransactionStatus status) {
                return jdbcOperations.execute(new ConnectionCallback<Object>() {
                    @Override
                    public Object doInConnection(Connection con) throws SQLException, DataAccessException {
                        final String userAgent;
                        if (userAgentArg.length() > 255) {
                            userAgent = userAgentArg.substring(0, 254);
                            log.debug("userAgent trimmed to 255 characters. userAgent: " + userAgentArg);
                        } else {
                            userAgent = userAgentArg;
                        }

                        // remove the old mapping and add the new one
                        PreparedStatement ps = null;
                        try {
                            ps = con.prepareStatement(
                                    "DELETE FROM UP_USER_UA_MAP WHERE USER_ID=? AND USER_AGENT=?");
                            ps.setInt(1, userId);
                            ps.setString(2, userAgent);
                            ps.executeUpdate();
                        } finally {
                            try {
                                ps.close();
                            } catch (Exception e) {
                                //ignore
                            }
                        }
                        try {
                            log.debug("writing to UP_USER_UA_MAP: userId: " + userId + ", userAgent: " + userAgent
                                    + ", profileId: " + profileId);
                            ps = con.prepareStatement(
                                    "INSERT INTO UP_USER_UA_MAP (USER_ID,USER_AGENT,PROFILE_ID) VALUES (?,?,?)");
                            ps.setInt(1, userId);
                            ps.setString(2, userAgent);
                            ps.setInt(3, profileId);
                            ps.executeUpdate();
                        } finally {
                            try {
                                ps.close();
                            } catch (Exception e) {
                                //ignore
                            }
                        }

                        return null;
                    }
                });
            }
        });
    }

    /**
     * Save the user layout.
     * @param person
     * @param profile
     * @param layoutXML
     * @throws Exception
     */
    public void setUserLayout(final IPerson person, final IUserProfile profile, final Document layoutXML,
            final boolean channelsAdded) {
        final long startTime = System.currentTimeMillis();
        final int userId = person.getID();
        final int profileId = profile.getProfileId();

        this.transactionOperations.execute(new TransactionCallback<Object>() {
            @Override
            public Object doInTransaction(TransactionStatus status) {
                return jdbcOperations.execute(new ConnectionCallback<Object>() {
                    @Override
                    public Object doInConnection(Connection con) throws SQLException, DataAccessException {

                        int layoutId = 0;
                        ResultSet rs;

                        // Eventually we want to be able to just get layoutId from the
                        // profile, but because of the template user layouts we have to do this for now ...
                        layoutId = getLayoutID(userId, profileId);

                        boolean firstLayout = false;
                        if (layoutId == 0) {
                            // First personal layout for this user/profile
                            layoutId = 1;
                            firstLayout = true;
                        }

                        String sql = "DELETE FROM UP_LAYOUT_PARAM WHERE USER_ID=? AND LAYOUT_ID=?";
                        PreparedStatement pstmt = con.prepareStatement(sql);
                        try {
                            pstmt.clearParameters();
                            pstmt.setInt(1, userId);
                            pstmt.setInt(2, layoutId);
                            if (log.isDebugEnabled())
                                log.debug(sql);
                            pstmt.executeUpdate();
                        } finally {
                            pstmt.close();
                        }

                        sql = "DELETE FROM UP_LAYOUT_STRUCT WHERE USER_ID=? AND LAYOUT_ID=?";
                        pstmt = con.prepareStatement(sql);
                        try {
                            pstmt.clearParameters();
                            pstmt.setInt(1, userId);
                            pstmt.setInt(2, layoutId);
                            if (log.isDebugEnabled())
                                log.debug(sql);
                            pstmt.executeUpdate();
                        } finally {
                            pstmt.close();
                        }

                        PreparedStatement structStmt = con.prepareStatement("INSERT INTO UP_LAYOUT_STRUCT "
                                + "(USER_ID, LAYOUT_ID, STRUCT_ID, NEXT_STRUCT_ID, CHLD_STRUCT_ID,EXTERNAL_ID,CHAN_ID,NAME,TYPE,HIDDEN,IMMUTABLE,UNREMOVABLE) "
                                + "VALUES (" + userId + "," + layoutId + ",?,?,?,?,?,?,?,?,?,?)");

                        PreparedStatement parmStmt = con.prepareStatement("INSERT INTO UP_LAYOUT_PARAM "
                                + "(USER_ID, LAYOUT_ID, STRUCT_ID, STRUCT_PARM_NM, STRUCT_PARM_VAL) " + "VALUES ("
                                + userId + "," + layoutId + ",?,?,?)");

                        int firstStructId;
                        try {
                            firstStructId = saveStructure(layoutXML.getFirstChild().getFirstChild(), structStmt,
                                    parmStmt);
                        } finally {
                            structStmt.close();
                            parmStmt.close();
                        }

                        //Check to see if the user has a matching layout
                        sql = "SELECT * FROM UP_USER_LAYOUT WHERE USER_ID=? AND LAYOUT_ID=?";
                        pstmt = con.prepareStatement(sql);
                        try {
                            pstmt.clearParameters();
                            pstmt.setInt(1, userId);
                            pstmt.setInt(2, layoutId);
                            if (log.isDebugEnabled())
                                log.debug(sql);
                            rs = pstmt.executeQuery();

                            try {
                                if (!rs.next()) {
                                    // If not, the default user is found and the layout rows from the default user are copied for the current user.
                                    int defaultUserId;

                                    sql = "SELECT USER_DFLT_USR_ID FROM UP_USER WHERE USER_ID=?";
                                    PreparedStatement pstmt2 = con.prepareStatement(sql);
                                    try {
                                        pstmt2.clearParameters();
                                        pstmt2.setInt(1, userId);
                                        if (log.isDebugEnabled())
                                            log.debug(sql);
                                        ResultSet rs2 = null;
                                        try {
                                            rs2 = pstmt2.executeQuery();
                                            rs2.next();
                                            defaultUserId = rs2.getInt(1);
                                        } finally {
                                            rs2.close();
                                        }
                                    } finally {
                                        pstmt2.close();
                                    }

                                    // Add to UP_USER_LAYOUT
                                    sql = "SELECT USER_ID,LAYOUT_ID,LAYOUT_TITLE,INIT_STRUCT_ID FROM UP_USER_LAYOUT WHERE USER_ID=?";
                                    pstmt2 = con.prepareStatement(sql);
                                    try {
                                        pstmt2.clearParameters();
                                        pstmt2.setInt(1, defaultUserId);
                                        if (log.isDebugEnabled())
                                            log.debug(sql);
                                        ResultSet rs2 = pstmt2.executeQuery();
                                        try {
                                            if (rs2.next()) {
                                                // There is a row for this user's template user...
                                                sql = "INSERT INTO UP_USER_LAYOUT (USER_ID, LAYOUT_ID, LAYOUT_TITLE, INIT_STRUCT_ID) VALUES (?,?,?,?)";
                                                PreparedStatement pstmt3 = con.prepareStatement(sql);
                                                try {
                                                    pstmt3.clearParameters();
                                                    pstmt3.setInt(1, userId);
                                                    pstmt3.setInt(2, rs2.getInt("LAYOUT_ID"));
                                                    pstmt3.setString(3, rs2.getString("LAYOUT_TITLE"));
                                                    pstmt3.setInt(4, rs2.getInt("INIT_STRUCT_ID"));
                                                    if (log.isDebugEnabled())
                                                        log.debug(sql);
                                                    pstmt3.executeUpdate();
                                                } finally {
                                                    pstmt3.close();
                                                }
                                            } else {
                                                // We can't rely on the template user, but we still need a row...
                                                sql = "INSERT INTO UP_USER_LAYOUT (USER_ID, LAYOUT_ID, LAYOUT_TITLE, INIT_STRUCT_ID) VALUES (?,?,?,?)";
                                                PreparedStatement pstmt3 = con.prepareStatement(sql);
                                                try {
                                                    pstmt3.clearParameters();
                                                    pstmt3.setInt(1, userId);
                                                    pstmt3.setInt(2, layoutId);
                                                    pstmt3.setString(3, "default layout");
                                                    pstmt3.setInt(4, 1);
                                                    if (log.isDebugEnabled())
                                                        log.debug(sql);
                                                    pstmt3.executeUpdate();
                                                } finally {
                                                    pstmt3.close();
                                                }
                                            }
                                        } finally {
                                            rs2.close();
                                        }
                                    } finally {
                                        pstmt2.close();
                                    }

                                }
                            } finally {
                                rs.close();
                            }
                        } finally {
                            pstmt.close();
                        }

                        //Update the users layout with the correct inital structure ID
                        sql = "UPDATE UP_USER_LAYOUT SET INIT_STRUCT_ID=? WHERE USER_ID=? AND LAYOUT_ID=?";
                        pstmt = con.prepareStatement(sql);
                        try {
                            pstmt.clearParameters();
                            pstmt.setInt(1, firstStructId);
                            pstmt.setInt(2, userId);
                            pstmt.setInt(3, layoutId);
                            if (log.isDebugEnabled())
                                log.debug(sql);
                            pstmt.executeUpdate();
                        } finally {
                            pstmt.close();
                        }

                        // Update the last time the user saw the list of available channels
                        if (channelsAdded) {
                            sql = "UPDATE UP_USER SET LST_CHAN_UPDT_DT=? WHERE USER_ID=?";
                            pstmt = con.prepareStatement(sql);
                            try {
                                pstmt.clearParameters();
                                pstmt.setDate(1, new java.sql.Date(System.currentTimeMillis()));
                                pstmt.setInt(2, userId);
                                log.debug(sql);
                                pstmt.executeUpdate();
                            } finally {
                                pstmt.close();
                            }
                        }

                        if (firstLayout) {
                            int defaultUserId;
                            int defaultLayoutId;
                            // Have to copy some of data over from the default user
                            sql = "SELECT USER_DFLT_USR_ID,USER_DFLT_LAY_ID FROM UP_USER WHERE USER_ID=?";
                            pstmt = con.prepareStatement(sql);
                            try {
                                pstmt.clearParameters();
                                pstmt.setInt(1, userId);
                                log.debug(sql);
                                rs = pstmt.executeQuery();
                                try {
                                    rs.next();
                                    defaultUserId = rs.getInt(1);
                                    defaultLayoutId = rs.getInt(2);
                                } finally {
                                    rs.close();
                                }
                            } finally {
                                pstmt.close();
                            }

                            sql = "UPDATE UP_USER_PROFILE SET LAYOUT_ID=1 WHERE USER_ID=? AND PROFILE_ID=?";
                            pstmt = con.prepareStatement(sql);
                            try {
                                pstmt.clearParameters();
                                pstmt.setInt(1, userId);
                                pstmt.setInt(2, profileId);
                                log.debug(sql);
                                pstmt.executeUpdate();
                            } finally {
                                pstmt.close();
                            }
                        }

                        return null;
                    }
                });
            }
        });
        if (log.isDebugEnabled()) {
            long stopTime = System.currentTimeMillis();
            log.debug("RDBMUserLayoutStore::setUserLayout(): Layout document for user " + userId + " took "
                    + (stopTime - startTime) + " milliseconds to save");
        }
    }

    public void updateUserProfile(final IPerson person, final IUserProfile profile) {
        final int userId = person.getID();
        this.transactionOperations.execute(new TransactionCallback<Object>() {
            @Override
            public Object doInTransaction(TransactionStatus status) {
                return jdbcOperations.execute(new ConnectionCallback<Object>() {
                    @Override
                    public Object doInConnection(Connection con) throws SQLException, DataAccessException {

                        String query = "UPDATE UP_USER_PROFILE SET LAYOUT_ID=?,THEME_SS_ID=?,STRUCTURE_SS_ID=?,"
                                + "DESCRIPTION=?,PROFILE_NAME=?, PROFILE_FNAME=? WHERE USER_ID=? AND PROFILE_ID=?";
                        PreparedStatement pstmt = con.prepareStatement(query);
                        pstmt.setInt(1, profile.getLayoutId());
                        pstmt.setInt(2, profile.getThemeStylesheetId());
                        pstmt.setInt(3, profile.getStructureStylesheetId());
                        pstmt.setString(4, profile.getProfileDescription());
                        pstmt.setString(5, profile.getProfileName());
                        pstmt.setString(6, profile.getProfileFname());
                        pstmt.setInt(7, userId);
                        pstmt.setInt(8, profile.getProfileId());
                        try {
                            if (log.isDebugEnabled())
                                log.debug("RDBMUserLayoutStore::updateUserProfile() : " + query + " layout_id: "
                                        + profile.getLayoutId() + " theme_ss_id: " + profile.getThemeStylesheetId()
                                        + " structure_ss_id: " + profile.getStructureStylesheetId()
                                        + " description: " + profile.getProfileDescription() + " name: "
                                        + profile.getProfileName() + " user_id: " + userId + " fname: "
                                        + profile.getProfileFname());
                            pstmt.execute();
                        } finally {
                            pstmt.close();
                        }

                        return null;
                    }
                });
            }
        });
    }

    public void setSystemBrowserMapping(String userAgent, int profileId) {
        this.setUserBrowserMapping(this.getSystemUser(), userAgent, profileId);
    }

    private String getSystemBrowserMapping(String userAgent) {
        return getUserBrowserMapping(this.getSystemUser(), userAgent);
    }

    public IUserProfile getUserProfile(IPerson person, String userAgent) {
        String profileFname = getUserBrowserMapping(person, userAgent);
        if (profileFname == null)
            return null;
        return this.getUserProfileByFname(person, profileFname);
    }

    public IUserProfile getSystemProfile(String userAgent) {
        String profileFname = getSystemBrowserMapping(userAgent);
        if (profileFname == null)
            return null;
        IUserProfile up = this.getUserProfileByFname(this.getSystemUser(), profileFname);
        up.setSystemProfile(true);
        return up;
    }

    public IUserProfile getSystemProfileById(int profileId) {
        IUserProfile up = this.getUserProfileById(this.getSystemUser(), profileId);
        up.setSystemProfile(true);
        return up;
    }

    public IUserProfile getSystemProfileByFname(String profileFname) {
        IUserProfile up = this.getUserProfileByFname(this.getSystemUser(), profileFname);
        up.setSystemProfile(true);
        return up;
    }

    public Hashtable getSystemProfileList() {
        Hashtable pl = this.getUserProfileList(this.getSystemUser());
        for (Enumeration e = pl.elements(); e.hasMoreElements();) {
            IUserProfile up = (IUserProfile) e.nextElement();
            up.setSystemProfile(true);
        }
        return pl;
    }

    public void updateSystemProfile(IUserProfile profile) {
        this.updateUserProfile(this.getSystemUser(), profile);
    }

    public IUserProfile addSystemProfile(IUserProfile profile) {
        return addUserProfile(this.getSystemUser(), profile);
    }

    public void deleteSystemProfile(int profileId) {
        this.deleteUserProfile(this.getSystemUser(), profileId);
    }

    private static class SystemUser implements IPerson {
        private final int systemUserId;

        public SystemUser(int systemUserId) {
            this.systemUserId = systemUserId;
        }

        public void setID(int sID) {
        }

        public int getID() {
            return this.systemUserId;
        }

        public String getUserName() {
            return null;
        }

        public void setUserName(String userName) {

        }

        public void setFullName(String sFullName) {
        }

        public String getFullName() {
            return "uPortal System Account";
        }

        public Object getAttribute(String key) {
            return null;
        }

        public Object[] getAttributeValues(String key) {
            return null;
        }

        public Map<String, List<Object>> getAttributeMap() {
            return null;
        }

        public void setAttribute(String key, Object value) {
        }

        public void setAttribute(String key, List<Object> values) {
        }

        public void setAttributes(Map attrs) {
        }

        public Enumeration getAttributes() {
            return null;
        }

        public Enumeration getAttributeNames() {
            return null;
        }

        public boolean isGuest() {
            return (false);
        }

        public ISecurityContext getSecurityContext() {
            return (null);
        }

        public void setSecurityContext(ISecurityContext context) {
        }

        public EntityIdentifier getEntityIdentifier() {
            return null;
        }

        public void setEntityIdentifier(EntityIdentifier ei) {
        }

        public String getName() {
            return null;
        }
    }

    /**
     * Returns the current layout ID for the user and profile. If the profile doesn't exist or the
     * layout_id field is null 0 is returned.
     *
     * @param userId The userId for the profile
     * @param profileId The profileId for the profile
     * @return The layout_id field or 0 if it does not exist or is null
     * @throws SQLException
     */
    protected int getLayoutID(final int userId, final int profileId) throws SQLException {
        return jdbcOperations.execute(new ConnectionCallback<Integer>() {
            @Override
            public Integer doInConnection(Connection con) throws SQLException, DataAccessException {

                String query = "SELECT LAYOUT_ID " + "FROM UP_USER_PROFILE " + "WHERE USER_ID=? AND PROFILE_ID=?";

                int layoutId = 0;

                PreparedStatement pstmt = con.prepareStatement(query);

                try {
                    final int u = userId;
                    final int p = profileId;
                    if (log.isDebugEnabled())
                        log.debug("RDBMUserLayoutStore::getLayoutID(userId=" + u + ", profileId=" + p + " ): "
                                + query);

                    pstmt.setInt(1, u);
                    pstmt.setInt(2, p);
                    ResultSet rs = pstmt.executeQuery();

                    try {
                        if (rs.next()) {
                            layoutId = rs.getInt(1);

                            if (rs.wasNull()) {
                                layoutId = 0;
                            }
                        }

                        // find the layout used by the default profle for this user
                        query = "SELECT LAYOUT_ID FROM UP_USER_PROFILE " + "WHERE USER_ID=? AND PROFILE_FNAME='"
                                + DEFAULT_LAYOUT_FNAME + "'";
                        pstmt = con.prepareStatement(query);
                        pstmt.setInt(1, userId);
                        rs = pstmt.executeQuery();
                        int intendedLayoutId = 0;
                        if (rs.next()) {
                            intendedLayoutId = rs.getInt("LAYOUT_ID");
                            if (rs.wasNull()) {
                                intendedLayoutId = 0;
                            }
                        }

                        // check to see if this profile for the current user
                        // has already used the requested layout
                        query = "SELECT LAYOUT_ID FROM UP_USER_PROFILE WHERE "
                                + "USER_ID=? AND LAYOUT_ID=? AND PROFILE_ID=?";
                        pstmt = con.prepareStatement(query);
                        pstmt.setInt(1, userId);
                        pstmt.setInt(2, intendedLayoutId);
                        pstmt.setInt(3, profileId);
                        rs = pstmt.executeQuery();
                        if (!rs.next()) {
                            // if the layout's not already been used, update the profile to
                            // point to that layout
                            query = "UPDATE UP_USER_PROFILE SET LAYOUT_ID=? WHERE " + "USER_ID=? AND PROFILE_ID=?";

                            pstmt = con.prepareStatement(query);

                            pstmt.setInt(1, intendedLayoutId);
                            pstmt.setInt(2, userId);
                            pstmt.setInt(3, profileId);
                            pstmt.execute();

                            layoutId = intendedLayoutId;

                        }

                    } finally {
                        rs.close();
                    }
                } finally {
                    pstmt.close();
                }

                return layoutId;

            }
        });
    }

    /* (non-Javadoc)
     * @see org.jasig.portal.layout.IUserLayoutStore#importLayout(org.dom4j.Element)
     */
    public abstract void importLayout(org.dom4j.Element layout);

}