/**
* Copyright (c) 2009--2015 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.manager.channel;
import com.redhat.rhn.domain.channel.Channel;
import com.redhat.rhn.domain.channel.ChannelArch;
import com.redhat.rhn.domain.channel.ChannelFactory;
import com.redhat.rhn.domain.common.ChecksumType;
import com.redhat.rhn.domain.user.User;
import com.redhat.rhn.frontend.xmlrpc.InvalidChannelLabelException;
import com.redhat.rhn.frontend.xmlrpc.InvalidChannelNameException;
import com.redhat.rhn.frontend.xmlrpc.InvalidChecksumLabelException;
import com.redhat.rhn.frontend.xmlrpc.InvalidGPGKeyException;
import com.redhat.rhn.frontend.xmlrpc.InvalidGPGUrlException;
import com.redhat.rhn.frontend.xmlrpc.InvalidParentChannelException;
import org.apache.commons.lang.StringUtils;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
/**
* CreateChannelCommand - command to create a new channel.
*/
public class CreateChannelCommand {
public static final int CHANNEL_NAME_MIN_LENGTH = 6;
public static final int CHANNEL_NAME_MAX_LENGTH = 256;
public static final int CHANNEL_LABEL_MIN_LENGTH = 6;
protected static final String CHANNEL_NAME_REGEX =
"^[a-zA-Z][\\w\\d\\s\\-\\.\\'\\(\\)\\/\\_]*$";
protected static final String CHANNEL_LABEL_REGEX =
"^[a-z\\d][a-z\\d\\-\\.\\_]*$";
// we ignore case with the red hat regex
protected static final String REDHAT_REGEX = "^(rhn|red\\s*hat)";
protected static final String GPG_KEY_REGEX = "^[0-9A-F]{8}$";
protected static final String GPG_URL_REGEX = "^(HTTPS?|FILE)://.*?$";
protected static final String GPG_FP_REGEX = "^(\\s*[0-9A-F]{4}\\s*){10}$";
protected static final String WEB_CHANNEL_CREATED = "web.channel_created";
protected User user;
protected String label;
protected String name;
protected String summary;
protected String description;
protected String archLabel;
protected String parentLabel;
protected Long parentId;
protected String gpgKeyUrl;
protected String gpgKeyId;
protected String gpgKeyFp;
protected String checksum;
protected String maintainerName;
protected String maintainerEmail;
protected String maintainerPhone;
protected String supportPolicy;
protected String access = Channel.PRIVATE;
protected boolean globallySubscribable = true;
/**
* default constructor.
*/
public CreateChannelCommand() {
user = null;
label = null;
name = null;
summary = null;
archLabel = null;
checksum = null;
parentLabel = null;
parentId = null;
}
/**
* @param archLabelIn The archLabel to set.
*/
public void setArchLabel(String archLabelIn) {
archLabel = archLabelIn;
}
/**
* @param labelIn The label to set.
*/
public void setLabel(String labelIn) {
label = labelIn;
}
/**
* @param nameIn The name to set.
*/
public void setName(String nameIn) {
name = nameIn;
}
/**
* @param checksumLabelIn The name to set.
*/
public void setChecksumLabel(String checksumLabelIn) {
this.checksum = checksumLabelIn;
}
/**
* @param parentLabelIn The parentLabel to set.
*/
public void setParentLabel(String parentLabelIn) {
parentLabel = parentLabelIn;
}
/**
* @param pid The parent id to set.
*/
public void setParentId(Long pid) {
parentId = pid;
}
/**
* @param fp gpgkey fingerprint
*/
public void setGpgKeyFp(String fp) {
gpgKeyFp = fp;
}
/**
* @param id gpgkey id
*/
public void setGpgKeyId(String id) {
gpgKeyId = id;
}
/**
* @param url gpgkey url
*/
public void setGpgKeyUrl(String url) {
gpgKeyUrl = url;
}
/**
* @param email maintainer's email address
*/
public void setMaintainerEmail(String email) {
maintainerEmail = email;
}
/**
* @param mname maintainers name
*/
public void setMaintainerName(String mname) {
maintainerName = mname;
}
/**
* @param phone maintainer's phone number (string)
*/
public void setMaintainerPhone(String phone) {
maintainerPhone = phone;
}
/**
* @param policy support policy
*/
public void setSupportPolicy(String policy) {
supportPolicy = policy;
}
/**
* @param summaryIn The summary to set.
*/
public void setSummary(String summaryIn) {
summary = summaryIn;
}
/**
* @param desc The description.
*/
public void setDescription(String desc) {
description = desc;
}
/**
* @param userIn The user to set.
*/
public void setUser(User userIn) {
user = userIn;
}
/**
* @param acc public, protected, or private
*/
public void setAccess(String acc) {
if (acc == null || acc.equals("")) {
access = Channel.PRIVATE;
}
else {
access = acc;
}
}
/**
* @param globallySubscribableIn if the channel should be globally subscribable
*/
public void setGloballySubscribable(boolean globallySubscribableIn) {
globallySubscribable = globallySubscribableIn;
}
protected void validateChannel(ChannelArch ca, ChecksumType ct) {
verifyRequiredParameters();
verifyChannelName(name);
verifyChannelLabel(label);
verifyGpgInformation();
if (ChannelFactory.doesChannelNameExist(name)) {
throw new InvalidChannelNameException(name,
InvalidChannelNameException.Reason.NAME_IN_USE,
"edit.channel.invalidchannelname.nameinuse", name);
}
if (ChannelFactory.doesChannelLabelExist(label)) {
throw new InvalidChannelLabelException(label,
InvalidChannelLabelException.Reason.LABEL_IN_USE,
"edit.channel.invalidchannellabel.labelinuse", label);
}
if (ca == null) {
throw new IllegalArgumentException("Invalid architecture label");
}
if (ct == null) {
throw new InvalidChecksumLabelException();
}
}
/**
* Creates the Channel based on the parameters that were set.
* @return the newly created Channel
* @throws InvalidChannelLabelException thrown if label is in use or invalid.
* @throws InvalidChannelNameException throw if name is in use or invalid.
* @throws IllegalArgumentException thrown if label, name or user are null.
* @throws InvalidParentChannelException thrown if parent label is not a
* valid base channel.
*/
public Channel create() throws InvalidChannelLabelException,
InvalidChannelNameException, InvalidParentChannelException {
ChannelArch ca = ChannelFactory.findArchByLabel(archLabel);
ChecksumType ct = ChannelFactory.findChecksumTypeByLabel(checksum);
validateChannel(ca, ct);
Channel c = ChannelFactory.createChannel();
c.setLabel(label);
c.setName(name);
c.setSummary(summary);
c.setDescription(description);
c.setOrg(user.getOrg());
c.setBaseDir("/dev/null");
c.setChannelArch(ca);
// handles either parent id or label
setParentChannel(c, user, parentLabel, parentId);
c.setChecksumType(ct);
c.setGPGKeyId(gpgKeyId);
c.setGPGKeyUrl(gpgKeyUrl);
c.setGPGKeyFp(gpgKeyFp);
c.setAccess(access);
c.setMaintainerName(maintainerName);
c.setMaintainerEmail(maintainerEmail);
c.setMaintainerPhone(maintainerPhone);
c.setSupportPolicy(supportPolicy);
c.addChannelFamily(user.getOrg().getPrivateChannelFamily());
// need to save before calling stored proc below
ChannelFactory.save(c);
// this ends up being a mode query call, must have saved the channel to get an id
c.setGloballySubscribable(globallySubscribable, user.getOrg());
ChannelManager.queueChannelChange(c.getLabel(), "createchannel", "createchannel");
ChannelFactory.refreshNewestPackageCache(c, WEB_CHANNEL_CREATED);
return c;
}
/**
* sets the parent channel of the given affected channel if pLabel or pid
* is given. pLabel is preferred if both are given. If both pLabel and
* pid are null or if no channel is found for the given label or pid, the
* affected channel is unchanged.
* @param affected The Channel to receive a new parent, if one is found.
* @param usr The usr
* @param lbl The parent Channel label, can be null.
* @param pid The parent Channel id, can be null.
*/
protected void setParentChannel(Channel affected, User usr,
String lbl, Long pid) {
Channel parent = null;
if ((lbl == null || lbl.equals("")) &&
pid == null) {
// these are not the droids you seek
return;
}
if (lbl != null && !lbl.equals("")) {
parent = ChannelManager.lookupByLabelAndUser(lbl, usr);
}
else if (pid != null) {
parent = ChannelManager.lookupByIdAndUser(pid, usr);
}
if (parent == null) {
throw new IllegalArgumentException("Invalid Parent Channel lbl");
}
if (!parent.isBaseChannel()) {
throw new InvalidParentChannelException();
}
// ensure child channel arch is compatible
ChannelArch ca = affected.getChannelArch();
if (parent != null) {
List<Map<String, String>> compatibleArches = ChannelManager
.compatibleChildChannelArches(parent.getChannelArch().getLabel());
Set<String> compatibleArchLabels = new HashSet<String>();
for (Map<String, String> arch : compatibleArches) {
compatibleArchLabels.add(arch.get("label"));
}
if (!compatibleArchLabels.contains(ca.getLabel())) {
throw new IllegalArgumentException(
"Incompatible parent and child channel architectures");
}
}
// man that's a lot of conditionals :) finally we do what
// we came here to do.
affected.setParentChannel(parent);
}
/**
* Verifies that the required parameters are not null.
* @throws IllegalArgumentException thrown if label, name, user or summary
* are null.
*/
protected void verifyRequiredParameters() {
if (user == null || StringUtils.isEmpty(summary)) {
throw new IllegalArgumentException(
"edit.channel.invalidchannelsummary");
}
}
protected void verifyChannelName(String cname) throws InvalidChannelNameException {
if (user == null) {
// can never be too careful
throw new IllegalArgumentException("Required param [user] is null");
}
if (cname == null || cname.trim().length() == 0) {
throw new InvalidChannelNameException(cname,
InvalidChannelNameException.Reason.IS_MISSING,
"edit.channel.invalidchannelname.missing", "");
}
if (!Pattern.compile(CHANNEL_NAME_REGEX).matcher(cname).find()) {
throw new InvalidChannelNameException(cname,
InvalidChannelNameException.Reason.REGEX_FAILS,
"edit.channel.invalidchannelname.supportedregex", "");
}
if (cname.length() < CHANNEL_NAME_MIN_LENGTH) {
Integer minLength = new Integer(CreateChannelCommand.CHANNEL_NAME_MIN_LENGTH);
throw new InvalidChannelNameException(cname,
InvalidChannelNameException.Reason.TOO_SHORT,
"edit.channel.invalidchannelname.minlength",
minLength.toString());
}
if (cname.length() > CHANNEL_NAME_MAX_LENGTH) {
Integer maxLength = new Integer(CreateChannelCommand.CHANNEL_NAME_MAX_LENGTH);
throw new InvalidChannelNameException(cname,
InvalidChannelNameException.Reason.TOO_LONG,
"edit.channel.invalidchannelname.maxlength",
maxLength.toString());
}
// the perl code used to ignore case with a /i at the end of
// the regex, so we toLowerCase() the channel name to make it
// work the same.
Matcher redhatRegex = Pattern.compile(REDHAT_REGEX).matcher(cname.toLowerCase());
if (redhatRegex.find()) {
throw new InvalidChannelNameException(cname,
InvalidChannelNameException.Reason.REDHAT_REGEX_FAILS,
"edit.channel.invalidchannelname.redhat", redhatRegex.group());
}
}
protected void verifyChannelLabel(String clabel) throws InvalidChannelLabelException {
if (user == null) {
// can never be too careful
throw new IllegalArgumentException("Required param is null");
}
if (clabel == null || clabel.trim().length() == 0) {
throw new InvalidChannelLabelException(clabel,
InvalidChannelLabelException.Reason.IS_MISSING,
"edit.channel.invalidchannellabel.missing", "");
}
if (!Pattern.compile(CHANNEL_LABEL_REGEX).matcher(clabel).find()) {
throw new InvalidChannelLabelException(clabel,
InvalidChannelLabelException.Reason.REGEX_FAILS,
"edit.channel.invalidchannellabel.supportedregex", "");
}
if (clabel.length() < CHANNEL_LABEL_MIN_LENGTH) {
Integer minLength = new Integer(CreateChannelCommand.CHANNEL_LABEL_MIN_LENGTH);
throw new InvalidChannelLabelException(clabel,
InvalidChannelLabelException.Reason.TOO_SHORT,
"edit.channel.invalidchannellabel.minlength",
minLength.toString());
}
// the perl code used to ignore case with a /i at the end of
// the regex, so we toLowerCase() the channel name to make it
// work the same.
Matcher redhatRegex = Pattern.compile(REDHAT_REGEX).matcher(clabel.toLowerCase());
if (redhatRegex.find()) {
throw new InvalidChannelLabelException(clabel,
InvalidChannelLabelException.Reason.REDHAT_REGEX_FAILS,
"edit.channel.invalidchannellabel.redhat", redhatRegex.group());
}
}
protected void verifyGpgInformation() {
if (StringUtils.isNotEmpty(gpgKeyId)) {
gpgKeyId = gpgKeyId.toUpperCase();
if (!Pattern.compile(GPG_KEY_REGEX).matcher(gpgKeyId).find()) {
throw new InvalidGPGKeyException();
}
}
if (StringUtils.isNotEmpty(gpgKeyFp)) {
gpgKeyFp = gpgKeyFp.toUpperCase();
if (!Pattern.compile(GPG_FP_REGEX).matcher(gpgKeyFp).find()) {
throw new InvalidGPGFingerprintException();
}
}
if (StringUtils.isNotEmpty(gpgKeyUrl)) {
// file: URLs can be case-sensitive, can't blindly uppercase here
String tmp = gpgKeyUrl.toUpperCase();
if (!Pattern.compile(GPG_URL_REGEX).matcher(tmp).find()) {
throw new InvalidGPGUrlException();
}
}
}
}