com.redhat.rhn.domain.config.ConfigurationFactory.java Source code

Java tutorial

Introduction

Here is the source code for com.redhat.rhn.domain.config.ConfigurationFactory.java

Source

/**
 * Copyright (c) 2009--2014 Red Hat, Inc.
 *
 * This software is licensed to you under the GNU General Public License,
 * version 2 (GPLv2). There is NO WARRANTY for this software, express or
 * implied, including the implied warranties of MERCHANTABILITY or FITNESS
 * FOR A PARTICULAR PURPOSE. You should have received a copy of GPLv2
 * along with this software; if not, see
 * http://www.gnu.org/licenses/old-licenses/gpl-2.0.txt.
 *
 * Red Hat trademarks are not licensed under GPLv2. No permission is
 * granted to use or replicate Red Hat trademarks that are incorporated
 * in this software or its documentation.
 */
package com.redhat.rhn.domain.config;

import com.redhat.rhn.common.db.datasource.CallableMode;
import com.redhat.rhn.common.db.datasource.DataResult;
import com.redhat.rhn.common.db.datasource.ModeFactory;
import com.redhat.rhn.common.db.datasource.SelectMode;
import com.redhat.rhn.common.hibernate.HibernateFactory;
import com.redhat.rhn.common.localization.LocalizationService;
import com.redhat.rhn.common.util.SHA256Crypt;
import com.redhat.rhn.domain.common.Checksum;
import com.redhat.rhn.domain.common.ChecksumFactory;
import com.redhat.rhn.domain.org.Org;
import com.redhat.rhn.domain.server.Server;
import com.redhat.rhn.domain.user.User;

import org.apache.commons.lang.StringUtils;
import org.apache.log4j.Logger;
import org.hibernate.ObjectNotFoundException;
import org.hibernate.Query;
import org.hibernate.Session;
import org.hibernate.criterion.Restrictions;

import java.io.IOException;
import java.io.InputStream;
import java.sql.Types;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
 * ConfigurationFactory.  For use when dealing with ConfigChannel, ConfigChannelType,
 * ConfigFile, ConfigRevision, ConfigFileState, ConfigContent, and ConfigInfo.
 *
 * When saving config channels, config files, and config revisions: please use the
 * commitConfigBlah methods.
 * @version $Rev$
 */
public class ConfigurationFactory extends HibernateFactory {
    private static ConfigurationFactory singleton = new ConfigurationFactory();
    private static Logger log = Logger.getLogger(ConfigurationFactory.class);

    private ConfigurationFactory() {
        super();
    }

    @Override
    protected Logger getLogger() {
        return log;
    }

    /**
     * Create a new ConfigFile object.
     * @return new ConfigFile object
     */
    public static ConfigFile newConfigFile() {
        return new ConfigFile();
    }

    /**
     * Create a new ConfigRevision object.
     * @return new ConfigRevision object
     */
    public static ConfigRevision newConfigRevision() {
        ConfigRevision cr = new ConfigRevision();
        Date now = new Date();
        cr.setRevision(new Long(1));
        cr.setCreated(now);
        cr.setModified(now);
        return cr;
    }

    /**
     * Create a new ConfigChannel object.
     * @return new ConfigChannel object
     */
    public static ConfigChannel newConfigChannel() {
        return new ConfigChannel();
    }

    /**
     * Create a new ConfigContent object.
     * @return new ConfigContent object
     */
    public static ConfigContent newConfigContent() {
        return new ConfigContent();
    }

    /**
     * Create and save a new configuration channel.
     * This method creates a configuration channel and then uses the
     * saveNewConfigChannel(ConfigChannel) method to save it.
     * @param org The org for this configuration channel.
     * @param type The type.  Please use the constants located in this class.
     * @param name The name of this configuration channel.
     * @param label The label for this configuration channel.
     * @param description The description of this configuration channel.
     * @return The newly saved configuration channel.
     */
    public static ConfigChannel saveNewConfigChannel(Org org, ConfigChannelType type, String name, String label,
            String description) {
        ConfigChannel out = new ConfigChannel();
        out.setOrg(org);
        out.setName(name);
        out.setLabel(label);
        out.setDescription(description);
        out.setConfigChannelType(type);
        saveNewConfigChannel(out);
        return out;
    }

    /**
     * Save a new configuration channel.
     * Note, this method uses a stored procedure, so it must be used for all newly
     * created configuration channels.
     * @param channel The channel object to persist.
     */
    public static void saveNewConfigChannel(ConfigChannel channel) {
        CallableMode m = ModeFactory.getCallableMode("config_queries", "create_new_config_channel");
        Map inParams = new HashMap();
        Map outParams = new HashMap();

        inParams.put("org_id_in", channel.getOrgId());
        inParams.put("type_in", channel.getConfigChannelType().getLabel());
        inParams.put("name_in", channel.getName());
        inParams.put("label_in", channel.getLabel());
        inParams.put("description_in", channel.getDescription());
        //Outparam
        outParams.put("channelId", new Integer(Types.NUMERIC));

        Map result = m.execute(inParams, outParams);

        Long channelId = (Long) result.get("channelId");
        channel.setId(channelId);
    }

    /**
     * Save a new configuration file.
     * Note, this method uses a stored procedure, so it must be used for all newly
     * created configuration files.
     * NOTE: This configuration file must have a persisted configuration channel
     *       attached to it.  config channels also used stored procedures for
     *       insertions, so we can't simply ask hibernate to save it for us.
     * @param file The configuration file to persist
     * @return config file id
     */
    public static Long saveNewConfigFile(ConfigFile file) {
        //This is designed to catch some of the cases in which the config channel
        //was not saved before the config file.
        //There is still the possibility that the config channel hasn't been committed to
        //the database yet, but someone has set its id.  This should never happen from the
        //web site, but it might happen from tests.
        if (file.getConfigChannel() == null || file.getConfigChannel().getId() == null) {
            throw new IllegalStateException("Config Channels must be " + "saved before config files");
        }

        //Have to commit the configFileName before we commit the
        // ConfigFile so the stored proc will have an ID to work with
        singleton.saveObject(file.getConfigFileName());

        CallableMode m = ModeFactory.getCallableMode("config_queries", "create_new_config_file");
        Map inParams = new HashMap();
        Map outParams = new HashMap();

        //this will generate a foreign-key constraint violation if the config
        //channel is not already persisted.
        inParams.put("config_channel_id_in", file.getConfigChannel().getId());
        inParams.put("name_in", file.getConfigFileName().getPath());
        // Outparam
        outParams.put("configFileId", new Integer(Types.NUMERIC));

        Map result = m.execute(inParams, outParams);
        return (Long) result.get("configFileId");
    }

    /**
     * Save a new ConfigRevision.
     * Note, this method uses a stored procedure, so it must be used for all newly
     * created configuration revisions.
     * NOTE: This configuration revision must have a persisted config file
     *       attached to it.  config files also used stored procedures for
     *       insertions, so we can't simply ask hibernate to save it for us.
     * @param revision the new ConfigRevision we want to store.
     * @return returns revision id
     */
    public static Long saveNewConfigRevision(ConfigRevision revision) {
        //This is designed to catch some of the cases in which the config file
        //was not saved before the config revision.
        //There is still the possibility that the config file hasn't been committed to
        //the database yet, but someone has set its id.  This should never happen from the
        //web site, but it might happen from tests.
        if (revision.getConfigFile() == null || revision.getConfigFile().getId() == null) {
            throw new IllegalStateException("Config Channels must be " + "saved before config files");
        }

        CallableMode m = ModeFactory.getCallableMode("config_queries", "create_new_config_revision");

        if (revision.isFile()) {
            //We need to save the content first so that we have an id for
            // the stored procedure.
            singleton.saveObject(revision.getConfigContent());
        }
        //We do not have to save the ConfigInfo, because the info should always already be
        // in the database.  If this is not the case, please read the documentation for
        // lookupOrInsertConfigInfo(String, String, Long) and correct the problem.

        Map inParams = new HashMap();
        Map outParams = new HashMap();

        inParams.put("revision_in", revision.getRevision());
        inParams.put("config_file_id_in", revision.getConfigFile().getId());
        if (revision.isFile()) {
            inParams.put("config_content_id_in", revision.getConfigContent().getId());
        } else {
            inParams.put("config_content_id_in", null);
        }
        inParams.put("config_info_id_in", revision.getConfigInfo().getId());
        inParams.put("config_file_type_id", new Long(revision.getConfigFileType().getId()));

        // Outparam
        outParams.put("configRevisionId", new Integer(Types.NUMERIC));

        Map result = m.execute(inParams, outParams);

        return (Long) result.get("configRevisionId");
    }

    private static void save(ConfigChannel channel) {
        singleton.saveObject(channel);
    }

    private static void save(ConfigFile file) {
        singleton.saveObject(file);
    }

    private static void save(ConfigRevision revision) {
        singleton.saveObject(revision);
    }

    /**
     * Save or update a config channel.  Since config channels
     * use a stored procedure for inserting, we have to decide whether to
     * insert or update here.  If the channel's id is null, we insert.
     * @param channel The channel to save or update
     */
    public static void commit(ConfigChannel channel) {
        if (channel.getId() == null) {
            saveNewConfigChannel(channel);
        } else {
            save(channel);
        }
    }

    /**
     * Save or update a config file.  Since config files
     * use a stored procedure for inserting, we have to decide whether to
     * insert or update here.  If the file's id is null, we insert.
     * @param file The file to save or update
     * @return config file
     */
    public static ConfigFile commit(ConfigFile file) {
        commit(file.getConfigChannel());
        if (file.getId() == null) {
            Long fileId = saveNewConfigFile(file);
            file = (ConfigFile) getSession().get(ConfigFile.class, fileId);
        } else {
            save(file);
        }
        return file;
    }

    /**
     * Save or update a config revision.  Since config revisions
     * use a stored procedure for inserting, we have to decide whether to
     * insert or update here.  If the revision's id is null, we insert.
     * @param revision The revision to save or update
     * @return returns config revision (with set id)
     */
    public static ConfigRevision commit(ConfigRevision revision) {
        ConfigFile file = revision.getConfigFile();
        commit(file);
        if (revision.getId() == null) {
            // save changedById, because saveNewConfigRevision does not handle it
            // and set it after reload not to lose it
            Long changedById = revision.getChangedById();
            Long revId = saveNewConfigRevision(revision);
            revision = (ConfigRevision) getSession().get(ConfigRevision.class, revId);
            revision.setChangedById(changedById);
            file.setLatestConfigRevision(revision);
            //and now we have to save the file again
            //it would be nice to save it only once, but we require the file id
            //in order to save the revision and the latestConfigRevision is the
            //revision id.
            commit(file);
        } else {
            //ConfigInfos have a unique constraint for their four data fields.
            //The config info object associated with this revision may have been
            //changed, so we need to carefully not update the database record.

            String targetPath = revision.getConfigInfo().getTargetFileName() == null ? null
                    : revision.getConfigInfo().getTargetFileName().getPath();

            ConfigInfo info = lookupOrInsertConfigInfo(revision.getConfigInfo().getUsername(),
                    revision.getConfigInfo().getGroupname(), revision.getConfigInfo().getFilemode(),
                    revision.getConfigInfo().getSelinuxCtx(), targetPath);
            //if the object did not change, we now have two hibernate objects
            //with the same identifier.  Evict one so that hibernate doesn't get mad.
            getSession().evict(revision.getConfigInfo());
            revision.setConfigInfo(info);
        }
        // And now, because saveNewConfigRevision doesn't store -every-thing
        // about a revision, we have to commit it -again-.  Sigh.  See BZ212236
        save(revision);
        return revision;
    }

    /**
     * Lookup a ConfigChannel by its id
     * @param id The identifier for the ConfigChannel
     * @return the ConfigChannel found or null if not found.
     */
    public static ConfigChannel lookupConfigChannelById(Long id) {
        Session session = HibernateFactory.getSession();
        ConfigChannel c = (ConfigChannel) session.get(ConfigChannel.class, id);
        return c;
    }

    /**
     * Lookup a ConfigChannel by its label. A config channel
     * is uniquely identified by label, org id and channel type
     * @param label The label for the ConfigChannel
     * @param org the org to which the config channel belongs.
     * @param cct the config channel type of the config channel.
     * @return the ConfigChannel found or null if not found.
     */
    public static ConfigChannel lookupConfigChannelByLabel(String label, Org org, ConfigChannelType cct) {
        Session session = HibernateFactory.getSession();
        ConfigChannel c = (ConfigChannel) session.createCriteria(ConfigChannel.class)
                .add(Restrictions.eq("org", org)).add(Restrictions.eq("label", label))
                .add(Restrictions.eq("configChannelType", cct)).uniqueResult();
        return c;
    }

    /**
     * Lookup a ConfigFile by its id
     * @param id The identifier for the ConfigFile
     * @return the ConfigFile found or null if not found.
     */
    public static ConfigFile lookupConfigFileById(Long id) {
        Session session = HibernateFactory.getSession();
        ConfigFile c = (ConfigFile) session.get(ConfigFile.class, id);
        return c;
    }

    /**
     * Lookup a ConfigFile by its channel's id and config file name's id
     * @param channel The file's config channel id
     * @param name The file's config file name id
     * @return the ConfigFile found or null if not found.
     */
    public static ConfigFile lookupConfigFileByChannelAndName(Long channel, Long name) {
        Session session = HibernateFactory.getSession();
        Query query = session.getNamedQuery("ConfigFile.findByChannelAndName")
                .setLong("channel_id", channel.longValue()).setLong("name_id", name.longValue())
                .setLong("state_id", ConfigFileState.normal().getId().longValue())
                //Retrieve from cache if there
                .setCacheable(true);
        try {
            return (ConfigFile) query.uniqueResult();
        } catch (ObjectNotFoundException e) {
            return null;
        }
    }

    /**
     * Finds a ConfigRevision from the database with a given id.
     * @param id The identifier for the ConfigRevision
     * @return The sought for ConfigRevision or null if not found.
     */
    public static ConfigRevision lookupConfigRevisionById(Long id) {
        Session session = HibernateFactory.getSession();
        ConfigRevision a = (ConfigRevision) session.get(ConfigRevision.class, id);
        return a;
    }

    /**
     * Finds a ConfigRevision for a given ConfigFile and given revision id
     * @param cf The ConfigFile to look for.
     * @param revId The ConfigFile revision to look for.
     * @return ConfigRevision The sought for ConfigRevision.
     */
    public static ConfigRevision lookupConfigRevisionByRevId(ConfigFile cf, Long revId) {
        Session session = HibernateFactory.getSession();
        Query q = session.getNamedQuery("ConfigRevision.findByRevisionAndConfigFile");
        q.setLong("rev", revId.longValue());
        q.setEntity("cf", cf);
        return (ConfigRevision) q.uniqueResult();
    }

    /**
     * Finds configuration revisions for a given configuration file
     * @param cf The ConfigFile to look for.
     * @return List of configuration revisions for given configuration file.
     */
    public static List lookupConfigRevisions(ConfigFile cf) {
        Session session = HibernateFactory.getSession();
        Query q = session.getNamedQuery("ConfigRevision.findByConfigFile");
        q.setEntity("cf", cf);
        return q.list();
    }

    /**
     * Finds a ConfigInfo from the database with a given id.
     * @param id The identifier for the ConfigInfo
     * @return The sought for ConfigInfo or null if not found.
     */
    public static ConfigInfo lookupConfigInfoById(Long id) {
        Session session = HibernateFactory.getSession();
        ConfigInfo c = (ConfigInfo) session.get(ConfigInfo.class, id);
        return c;
    }

    /**
     * Finds a ConfigFileName from the database with a given id.
     * @param id The identifier for the ConfigFileName
     * @return The sought for ConfigFileName or null if not found.
     */
    public static ConfigFileName lookupConfigFileNameById(Long id) {
        Session session = HibernateFactory.getSession();
        ConfigFileName c = (ConfigFileName) session.get(ConfigFileName.class, id);
        return c;
    }

    /**
     * Used to look up ConfigChannelTypes.  Note: there is a static list of
     * ConfigChannelTypes and therefore a static list of labels.  This method
     * is private because there are public static member variables for each
     * ConfigChannelType
     * @param label The unique label of the type.
     * @return A sought for ConfigChannelType or null
     */
    static ConfigChannelType lookupConfigChannelTypeByLabel(String label) {
        Session session = HibernateFactory.getSession();
        return (ConfigChannelType) session.getNamedQuery("ConfigChannelType.findByLabel").setString("label", label)
                //Retrieve from cache if there
                .setCacheable(true).uniqueResult();
    }

    /**
     * Used to look up ConfigFileStates.  Note: there is a static list of
     * ConfigFileStates and therefore a static list of labels.  This method
     * is private because there are public static member variables for each
     * ConfigChannelType
     * @param label The unique label of the type.
     * @return A sought for ConfigFileState or null
     */
    static ConfigFileState lookupConfigFileStateByLabel(String label) {
        Session session = HibernateFactory.getSession();
        return (ConfigFileState) session.getNamedQuery("ConfigFileState.findByLabel").setString("label", label)
                //Retrieve from cache if there
                .setCacheable(true).uniqueResult();
    }

    /**
     * Returns the the config file types associted to the given label
     * @param label the filte type label
     * @return config filetype object
     */
    static ConfigFileType lookupConfigFileTypeByLabel(String label) {
        Session session = HibernateFactory.getSession();
        return (ConfigFileType) session.getNamedQuery("ConfigFileType.findByLabel").setString("label", label)
                //Retrieve from cache if there
                .setCacheable(true).uniqueResult();
    }

    /**
     * Return a <code>ConfigInfo</code> from the username, groupname, file mode, and
     * selinux context. If no corresponding entry exists yet in the database, one will be
     * created.
     *
     * Uses the stored procedure <code>lookup_config_info</code> to get the id of the
     * ConfigInfo and then uses hibernate to lookup using that id.
     *
     * Note: we should use the stored procedure because it is autonomous and avoids race
     * conditions. However, we also need to make sure that hibernate knows that the object
     * already exists in the database.  Therefore after storing it, instead of simply
     * creating the object in java, we ask hibernate to look it up (which will find the
     * correct created and modified dates as well).
     *
     * ConfigInfo's have a unique constraint around username, groupname, and filemode
     * so we can't just create them willy nilly.
     *
     * @param username The linux username associated with a file
     * @param groupname The linux groupname associated with a file
     * @param filemode The three digit file mode (ex: 655)
     * @param selinuxCtx The SELinux context
     * @param symlinkTargetPath a target path for symlink or null for a non symlink path
     * @return The ConfigInfo found or inserted.
     */
    public static ConfigInfo lookupOrInsertConfigInfo(String username, String groupname, Long filemode,
            String selinuxCtx, String symlinkTargetPath) {
        Long id = lookupConfigInfo(username, groupname, filemode, selinuxCtx, symlinkTargetPath);
        return lookupConfigInfoById(id);
    }

    /**
     * Using a stored procedure that looks up the config info and will
     * create one if it does not exist.
     * @param user The linux username associated with a file
     * @param group The linux groupname associated with a file
     * @param filemode The three digit file mode (ex: 655)
     * @param selinuxCtx The SELinux context
     * @return The id of the found config info
     */
    private static Long lookupConfigInfo(String user, String group, Long filemode, String selinuxCtx,
            String symlinkTargetPath) {
        CallableMode m = ModeFactory.getCallableMode("config_queries", "lookup_config_info");

        Map inParams = new HashMap();
        Map outParams = new HashMap();

        inParams.put("username_in", user);
        inParams.put("groupname_in", group);
        inParams.put("filemode_in", filemode);
        if ((selinuxCtx == null) || (selinuxCtx.isEmpty())) {
            inParams.put("selinuxCtx_in", null);
        } else {
            inParams.put("selinuxCtx_in", selinuxCtx);
        }
        if (!StringUtils.isBlank(symlinkTargetPath)) {
            ConfigFileName fn = lookupOrInsertConfigFileName(symlinkTargetPath);
            inParams.put("symlink_target_file_in", fn.getId());
        } else {
            inParams.put("symlink_target_file_in", null);
        }

        outParams.put("info_id", new Integer(Types.NUMERIC));

        Map out = m.execute(inParams, outParams);

        return (Long) out.get("info_id");
    }

    /**
     * Return a <code>ConfigFileName</code> for the path given. If no corresponding
     * entry exists yet in the database, one will be created.
     *
     * Uses the stored procedure <code>lookup_config_filename</code> to get the id of the
     * ConfigFileName and then uses hibernate to lookup using that id.
     *
     * Note: we should use the stored procedure because it is autonomous and avoids race
     * conditions. However, we also need to make sure that hibernate knows that the object
     * already exists in the database.  Therefore after storing it, instead of simply
     * creating the object in java, we ask hibernate to look it up (which will find the
     * correct created and modified dates as well).
     *
     * ConfigFileName's have a unique constraint around path so we can't just create
     * them willy nilly.
     *
     * @param path the path for the <code>ConfigFileName</code>
     * @return The <code>ConfigFileName</code> found
     */
    public static ConfigFileName lookupOrInsertConfigFileName(String path) {
        Long id = lookupConfigFileName(path);
        return lookupConfigFileNameById(id);
    }

    /**
     * Using a stored procedure that looks up the config file name and will
     * create one if it does not exist.
     * @param path the path for the <code>ConfigFileName</code>
     * @return The id of the found config file name
     */
    private static Long lookupConfigFileName(String path) {
        CallableMode m = ModeFactory.getCallableMode("config_queries", "lookup_config_filename");

        Map inParams = new HashMap();
        Map outParams = new HashMap();

        inParams.put("name_in", path);
        outParams.put("name_id", new Integer(Types.NUMERIC));

        Map out = m.execute(inParams, outParams);

        return (Long) out.get("name_id");
    }

    /**
     * Remove a ConfigChannel.
     * This uses a stored procedure and the stored procedure is required
     * as it performs logic to determine the amount of org quota is now
     * used and appropriately updates those tables.
     * @param channel Channel to remove
     */
    public static void removeConfigChannel(ConfigChannel channel) {
        CallableMode m = ModeFactory.getCallableMode("config_queries", "remove_config_channel");

        Map inParams = new HashMap();
        Map outParams = new HashMap();
        inParams.put("config_channel_id_in", channel.getId());
        m.execute(inParams, outParams);
    }

    /**
     * Remove a ConfigFile.
     * This uses a stored procedure and the stored procedure is required
     * as it performs logic to determine the amount of org quota is now
     * used and appropriately updates those tables.
     * @param file Config File to remove
     */
    public static void removeConfigFile(ConfigFile file) {
        CallableMode m = ModeFactory.getCallableMode("config_queries", "remove_config_file");

        Map inParams = new HashMap();
        Map outParams = new HashMap();
        inParams.put("config_file_id_in", file.getId());
        m.execute(inParams, outParams);
    }

    /**
     * Remove a ConfigRevision.
     * This uses a stored procedure and the stored procedure is required
     * as it performs logic to determine the amount of org quota is now
     * used and appropriately updates those tables.
     * @param revision Revision to remove
     * @param orgId The id for the org in which this revision is located.
     * @return whether the parent file was deleted too.
     */
    public static boolean removeConfigRevision(ConfigRevision revision, Long orgId) {
        boolean latest = false;
        ConfigFile file = revision.getConfigFile();
        //is this revision the latest revision?
        if (file.getLatestConfigRevision().getId().equals(revision.getId())) {
            latest = true;
        }

        CallableMode m = ModeFactory.getCallableMode("config_queries", "remove_config_revision");

        Map inParams = new HashMap();
        Map outParams = new HashMap();
        inParams.put("config_revision_id_in", revision.getId());
        inParams.put("org_id", orgId);
        m.execute(inParams, outParams);

        if (latest) {
            //We just deleted the latest revision and now the config file has no idea
            //what its latest revision is, so we will find out.
            Map map = getMaxRevisionForFile(file);
            if (map != null) {
                Long id = (Long) map.get("id");
                file.setLatestConfigRevision(lookupConfigRevisionById(id));
                commit(file);
            } else {
                //there are no revisions in this file, delete the file.
                removeConfigFile(file);
                return true;
            }
        }
        return false;
    }

    /**
     * Create a local config channel for the given server. Fills in the necessary
     * fields with standard information.
     * @param server The server used to help populate required fields
     * @param type The type of the config channel. Either sandbox or local override.
     * @return The new local config channel.
     */
    public static ConfigChannel createNewLocalChannel(Server server, ConfigChannelType type) {
        ConfigChannel retval = newConfigChannel();
        retval.setOrg(server.getOrg());
        retval.setConfigChannelType(type);
        retval.setCreated(new Date());
        retval.setModified(new Date());

        //The name of the channel should always be the server name for
        //local config channels.  See bug #203406
        retval.setName(server.getName());
        retval.setLabel(server.getId().toString());

        //This is an english string. However, users should never see a description of
        //a local config channel. For all purposes, this is a useless field that only
        //exists because we currently treat local config channels exactly the same as
        //global config channels.
        retval.setDescription("Auto-generated " + type.getLabel() + " config channel");

        //TODO: put the following line back. It is not here now because Server.findLocal
        //      does this task for us. It belongs here, but this would currently cause
        //      an infinite loop based on how setSandboxOverride works.
        //server.setSandboxOverride(retval);
        commit(retval);

        return retval;
    }

    /**
     * Creates a new revision object from the give input stream.  The size is set to
     * be the given size.  The revision object is placed into the given config file
     * and uses the meta-data from the latest revision in that file.
     * @param usr The user that is making the new revision
     * @param stream The input stream containing the content for this revision.
     * @param size The size of the input stream to be read.
     * @param file The parent object for this config revision.
     * @return The newly created config revision.
     */
    public static ConfigRevision createNewRevisionFromStream(User usr, InputStream stream, Long size,
            ConfigFile file) {
        //get a copy of the latest revision (to copy meta-data)
        ConfigRevision revision = file.getLatestConfigRevision().copy();

        /*
         * We need to make five changes to the current revision.
         * 1. increment the revision number
         * 2. replace the content
         * 3. magic-decide whether this revision is binary
         * 4. compute the md5sum
         * 5. give it a new id
         */

        //Step 1
        //For database integrity, we won't just increment the latest revision number and
        //hope that nobody has futzed around.  We will get the max revision and increment.
        Long next = getNextRevisionForFile(file);
        revision.setRevision(next);
        revision.setChangedById(usr.getId());

        //Steps 2-4
        if (revision.isFile()) {
            revision.setConfigContent(createNewContentFromStream(stream, size,
                    revision.getConfigContent().isBinary(), revision.getConfigContent().getDelimStart(),
                    revision.getConfigContent().getDelimEnd()));
        }

        //Step 5
        revision.setId(null);
        revision = commit(revision);

        file.setLatestConfigRevision(revision);
        commit(file);

        return revision;
    }

    /**
     * Creates a ConfigContent object whose BLOB is filled with the bytes from the
     * specified stream
     * @param stream stream containing the content
     * @param size number of bytes to read
     * @param isBinary true if the content is to be treated as binary (which means we
     * won't expand macros, morph EOL, or let you edit it from the web UI)
     * @param delimStart start delimeter or null for binary
     * @param delimEnd end delimeter or null for binary
     * @return filled-in ConfigContent
     */
    public static ConfigContent createNewContentFromStream(InputStream stream, Long size, boolean isBinary,
            String delimStart, String delimEnd) {
        ConfigContent content = ConfigurationFactory.newConfigContent();
        content.setCreated(new Date());
        content.setModified(new Date());
        content.setFileSize(size);

        byte[] foo = bytesFromStream(stream, size);
        content.setContents(foo);
        Checksum newChecksum = ChecksumFactory.safeCreate(SHA256Crypt.sha256Hex(foo), "sha256");
        content.setChecksum(newChecksum);
        content.setBinary(isBinary);
        content.setDelimStart(delimStart);
        content.setDelimEnd(delimEnd);
        return content;
    }

    /**
     * Convert input stream to byte array
     * @param stream input stream
     * @param size stream size
     * @return byte array
     */
    public static byte[] bytesFromStream(InputStream stream, Long size) {
        byte[] foo = new byte[size.intValue()];
        try {
            //this silly bit of logic is to ensure that we read as much from the file
            //as we possibly can.  Most likely, stream.read(foo) would do the exact same
            //thing, but according to the javadoc, that may not always be the case.

            int offset = 0;
            int read = 0;
            // mark and reset stream, so that stream can be re-read later
            stream.mark(size.intValue());
            do {
                read = stream.read(foo, offset, (foo.length - offset));
                offset += read;
            } while (read > 0 && offset < foo.length);
            stream.reset();
        } catch (IOException e) {
            log.error("IOException while reading config content from input stream!", e);
            throw new RuntimeException("IOException while reading config content from" + " input stream!");
        }
        return foo;
    }

    private static Map getMaxRevisionForFile(ConfigFile file) {
        Map<String, Object> params = new HashMap<String, Object>();
        params.put("cfid", file.getId());
        SelectMode m = ModeFactory.getMode("config_queries", "max_revision_for_file");
        DataResult dr = m.execute(params);
        if (dr.isEmpty()) {
            return null; //no revisions left.
        }
        return (Map) dr.get(0);
    }

    /**
     * Returns new revision number for config file
     * @param file config file
     * @return next revision number
     */
    public static Long getNextRevisionForFile(ConfigFile file) {
        Map results = getMaxRevisionForFile(file);
        Long next = new Long(((Long) results.get("revision")).longValue() + 1);
        return next;
    }

    /**
     * Copies the given config revision to the given channel.
     * If there is a candidate config file that exists in that channel, this
     * revision gets set as the newest revision there.
     * Otherwise, a new config file is created with this as its only revision.
     * @param usr The user asking for the new revision
     * @param revision The revision to be copied
     * @param channel The channel to be copied into
     */
    public static void copyRevisionToChannel(User usr, ConfigRevision revision, ConfigChannel channel) {
        /*
         * 1. Find any candidate config files already in the channel.
         * 2. Make a copy of the revision
         * 3. Associate the revision and the file
         * 4. save
         */

        //Step 1.
        ConfigFileName name = revision.getConfigFile().getConfigFileName();
        ConfigFile file = lookupConfigFileByChannelAndName(channel.getId(), name.getId());
        Long rev = null;
        if (file == null) { //if a candidate does not exist, create one.
            rev = new Long(1);
            file = ConfigurationFactory.newConfigFile();
            file.setConfigChannel(channel);
            file.setConfigFileName(name);
            file.setConfigFileState(ConfigFileState.normal());
            file.setCreated(new Date());
            file.setModified(new Date());
            file = commit(file);
        } else {
            rev = getNextRevisionForFile(file);
        }

        //Step 2
        ConfigRevision newRevision = revision.copy();
        newRevision.setRevision(rev);
        newRevision.setChangedById(usr.getId());
        newRevision.setId(null);

        //Step 3
        newRevision.setConfigFile(file);

        //Step 4
        newRevision = commit(newRevision);
        file.setLatestConfigRevision(newRevision);
        commit(file);
    }

    /**
     * Returns a localized string for the name of a config channel.  This is here
     * because local and sandbox config channels are created automatically and therefore
     * have english names.  This is located in this file to be a central location for
     * the logic.
     * @param type The type of the channel (one of the labels for
     *             CONFIG_CHANNEL_TYPE_* constants)
     * @param channel The name of the channel, the system name for local channels.
     * @return A localized string for channel name
     */
    public static String getChannelNameDisplay(String type, String channel) {
        if (type == null) {
            throw new IllegalArgumentException("Error: channel type cannot be null");
        }

        if (ConfigChannelType.global().getLabel().equals(type)) {
            return channel; //for global channels, there name is the channel name.
        } else if (ConfigChannelType.local().getLabel().equals(type)) {
            return LocalizationService.getInstance().getMessage("config_channel_name.local", channel);
        } else if (ConfigChannelType.sandbox().getLabel().equals(type)) {
            return LocalizationService.getInstance().getMessage("config_channel_name.sandbox", channel);
        } else {
            throw new IllegalArgumentException(
                    "Error getting channel name display." + " Invalid channel type given.");
        }
    }

}