/**
*
*/
package hudson.plugins.jabber.im.transport;
import hudson.Util;
import hudson.model.AbstractProject;
import hudson.model.Hudson;
import hudson.plugins.im.IMException;
import hudson.plugins.im.IMMessageTarget;
import hudson.plugins.im.IMMessageTargetConversionException;
import hudson.plugins.im.IMMessageTargetConverter;
import hudson.plugins.im.IMPublisherDescriptor;
import hudson.plugins.im.NotificationStrategy;
import hudson.plugins.im.tools.Assert;
import hudson.plugins.im.tools.ExceptionHelper;
import hudson.tasks.BuildStepDescriptor;
import hudson.tasks.Publisher;
import hudson.util.FormValidation;
import java.io.IOException;
import java.net.InetAddress;
import java.net.Socket;
import java.net.UnknownHostException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.logging.Logger;
import javax.servlet.http.HttpServletRequest;
import net.sf.json.JSONObject;
import org.acegisecurity.Authentication;
import org.acegisecurity.AuthenticationException;
import org.acegisecurity.providers.UsernamePasswordAuthenticationToken;
import org.apache.commons.lang.StringUtils;
import org.jivesoftware.smack.Roster.SubscriptionMode;
import org.kohsuke.stapler.QueryParameter;
import org.kohsuke.stapler.StaplerRequest;
public class JabberPublisherDescriptor extends BuildStepDescriptor<Publisher> implements IMPublisherDescriptor {
private static final Logger LOGGER = Logger.getLogger(JabberPublisherDescriptor.class.getName());
private static final String PREFIX = "jabberPlugin.";
public static final String PARAMETERNAME_ENABLED = JabberPublisherDescriptor.PREFIX + "enabled";
public static final String PARAMETERNAME_PORT = JabberPublisherDescriptor.PREFIX + "port";
public static final String PARAMETERNAME_HOSTNAME = JabberPublisherDescriptor.PREFIX + "hostname";
public static final String PARAMETERNAME_SSL = JabberPublisherDescriptor.PREFIX + "ssl";
public static final String PARAMETERNAME_SASL = JabberPublisherDescriptor.PREFIX + "enableSASL";
public static final String PARAMETERNAME_PRESENCE = JabberPublisherDescriptor.PREFIX + "exposePresence";
public static final String PARAMETERNAME_PASSWORD = JabberPublisherDescriptor.PREFIX + "password";
public static final String PARAMETERNAME_JABBERID = JabberPublisherDescriptor.PREFIX + "jabberId";
public static final String PARAMETERNAME_GROUP_NICKNAME = JabberPublisherDescriptor.PREFIX + "groupNick";
public static final String PARAMETERNAME_TARGETS = JabberPublisherDescriptor.PREFIX + "targets";
public static final String PARAMETERNAME_STRATEGY = JabberPublisherDescriptor.PREFIX + "strategy";
public static final String PARAMETERNAME_NOTIFY_START = JabberPublisherDescriptor.PREFIX + "notifyStart";
public static final String PARAMETERNAME_NOTIFY_SUSPECTS = JabberPublisherDescriptor.PREFIX + "notifySuspects";
public static final String PARAMETERNAME_NOTIFY_CULPRITS = JabberPublisherDescriptor.PREFIX + "notifyCulprits";
public static final String PARAMETERNAME_NOTIFY_FIXERS = JabberPublisherDescriptor.PREFIX + "notifyFixers";
public static final String PARAMETERNAME_NOTIFY_UPSTREAM_COMMITTERS = JabberPublisherDescriptor.PREFIX + "notifyUpstreamCommitters";
public static final String PARAMETERNAME_INITIAL_GROUPCHATS = JabberPublisherDescriptor.PREFIX + "initialGroupChats";
public static final String PARAMETERNAME_COMMAND_PREFIX = JabberPublisherDescriptor.PREFIX + "commandPrefix";
public static final String PARAMETERNAME_DEFAULT_ID_SUFFIX = JabberPublisherDescriptor.PREFIX + "defaultIdSuffix";
public static final String PARAMETERNAME_HUDSON_LOGIN = JabberPublisherDescriptor.PREFIX + "hudsonLogin";
public static final String PARAMETERNAME_HUDSON_PASSWORD = JabberPublisherDescriptor.PREFIX + "hudsonPassword";
public static final String PARAMETERNAME_SUBSCRIPTION_MODE = JabberPublisherDescriptor.PREFIX + "subscriptionMode";
public static final String[] PARAMETERVALUE_SUBSCRIPTION_MODE;
static {
SubscriptionMode[] modes = SubscriptionMode.values();
PARAMETERVALUE_SUBSCRIPTION_MODE = new String[modes.length];
for (int i=0; i < modes.length; i++) {
PARAMETERVALUE_SUBSCRIPTION_MODE[i] = modes[i].name();
}
}
public static final String[] PARAMETERVALUE_STRATEGY_VALUES = NotificationStrategy.getDisplayNames();
public static final String PARAMETERVALUE_STRATEGY_DEFAULT = NotificationStrategy.STATECHANGE_ONLY.getDisplayName();
public static final String DEFAULT_COMMAND_PREFIX = "!";
private static final int DEFAULT_PORT = 5222;
// big Boolean to support backwards compatibility
private Boolean enabled;
private int port = DEFAULT_PORT;
private String hostname;
/**
* Only left here for deserialization compatibility with old instances.
* @deprecated not supported anymore. Any half decent jabber server doesn't need this.
*/
@SuppressWarnings("unused")
@Deprecated
private transient boolean legacySSL;
// the following 2 are actually the Jabber nick and password. For backward compatibility I cannot rename them
private String hudsonNickname;
private String hudsonPassword;
private String groupChatNickname;
private boolean exposePresence = true;
private boolean enableSASL = true;
private String initialGroupChats;
private String commandPrefix = DEFAULT_COMMAND_PREFIX;
private String defaultIdSuffix;
private String hudsonCiLogin;
private String hudsonCiPassword;
private String subscriptionMode = SubscriptionMode.accept_all.name();
public JabberPublisherDescriptor()
{
super(JabberPublisher.class);
load();
if (isEnabled()) {
try {
JabberIMConnectionProvider.setDesc(this);
} catch (final Exception e) {
// Server temporarily unavailable or misconfigured?
LOGGER.warning(ExceptionHelper.dump(e));
}
} else {
try {
JabberIMConnectionProvider.setDesc(null);
} catch (IMException e) {
// ignore
LOGGER.info(ExceptionHelper.dump(e));
}
}
}
@Override
public void load() {
super.load();
if (this.enabled == null) {
// migrate from plugin < v1.0
if (Util.fixEmptyAndTrim(this.hudsonNickname) != null) {
this.enabled = Boolean.TRUE;
} else {
this.enabled = Boolean.FALSE;
}
}
if (this.subscriptionMode == null) {
this.subscriptionMode = SubscriptionMode.accept_all.name();
}
}
// TODO: reuse the checkHostAccessibility method for this
private void applyHostname(final HttpServletRequest req, boolean check) throws FormException
{
final String s = req.getParameter(JabberPublisherDescriptor.PARAMETERNAME_HOSTNAME);
if (check && (s != null) && (s.trim().length() > 0))
{
try
{
InetAddress.getByName(s); // try to resolve
this.hostname = s;
}
catch (final UnknownHostException e)
{
throw new FormException("Cannot find Host '" + s + "'.",
JabberPublisherDescriptor.PARAMETERNAME_HOSTNAME);
}
}
else
{
this.hostname = null;
}
}
private void applyNickname(final HttpServletRequest req, boolean check) throws FormException
{
this.hudsonNickname = req.getParameter(JabberPublisherDescriptor.PARAMETERNAME_JABBERID);
if (check) {
if ((this.hostname != null) && ((this.hudsonNickname == null) || (this.hudsonNickname.trim().length() == 0)))
{
throw new FormException("Account/Nickname cannot be empty.",
JabberPublisherDescriptor.PARAMETERNAME_JABBERID);
}
}
}
private void applyPassword(final HttpServletRequest req, boolean check) throws FormException
{
this.hudsonPassword = req.getParameter(JabberPublisherDescriptor.PARAMETERNAME_PASSWORD);
if (check) {
if (((this.hostname != null) && (this.hudsonPassword == null))
|| (this.hudsonPassword.trim().length() == 0)) {
throw new FormException("Password cannot be empty.", JabberPublisherDescriptor.PARAMETERNAME_PASSWORD);
}
}
}
private void applyGroupChatNickname(final HttpServletRequest req) throws FormException
{
this.groupChatNickname = req.getParameter(JabberPublisherDescriptor.PARAMETERNAME_GROUP_NICKNAME);
if (this.groupChatNickname != null && this.groupChatNickname.trim().length() == 0)
{
this.groupChatNickname = null;
}
}
private void applyPort(final HttpServletRequest req, boolean check) throws FormException
{
final String p = Util.fixEmptyAndTrim(req.getParameter(JabberPublisherDescriptor.PARAMETERNAME_PORT));
if (p != null)
{
try
{
final int i = Integer.parseInt(p);
if (check && ((i < 0) || (i > 65535))) {
throw new FormException("Port out of range.", JabberPublisherDescriptor.PARAMETERNAME_PORT);
}
this.port = i;
}
catch (final NumberFormatException e)
{
throw new FormException("Port cannot be parsed.", JabberPublisherDescriptor.PARAMETERNAME_PORT);
}
} else {
this.port = DEFAULT_PORT;
}
}
private void applyInitialGroupChats(final HttpServletRequest req) {
this.initialGroupChats = Util.fixEmptyAndTrim(req.getParameter(JabberPublisherDescriptor.PARAMETERNAME_INITIAL_GROUPCHATS));
}
private void applyCommandPrefix(final HttpServletRequest req) {
String prefix = req.getParameter(JabberPublisherDescriptor.PARAMETERNAME_COMMAND_PREFIX);
if ((prefix != null) && (prefix.trim().length() > 0)) {
this.commandPrefix = prefix;
} else {
this.commandPrefix = DEFAULT_COMMAND_PREFIX;
}
}
private void applyDefaultIdSuffix(final HttpServletRequest req) {
String suffix = req.getParameter(JabberPublisherDescriptor.PARAMETERNAME_DEFAULT_ID_SUFFIX);
if ((suffix != null) && (suffix.trim().length() > 0)) {
this.defaultIdSuffix = suffix.trim();
} else {
this.defaultIdSuffix = "";
}
}
private void applyHudsonLoginPassword(HttpServletRequest req) throws FormException {
this.hudsonCiLogin = Util.fixEmptyAndTrim(req.getParameter(PARAMETERNAME_HUDSON_LOGIN));
this.hudsonCiPassword = Util.fixEmptyAndTrim(req.getParameter(PARAMETERNAME_HUDSON_PASSWORD));
if(this.hudsonCiLogin != null) {
Authentication auth = new UsernamePasswordAuthenticationToken(this.hudsonCiLogin, this.hudsonCiPassword);
try {
Hudson.getInstance().getSecurityRealm().getSecurityComponents().manager.authenticate(auth);
} catch (AuthenticationException e) {
throw new FormException(e, "Bad Hudson credentials");
}
}
}
/**
* This human readable name is used in the configuration screen.
*/
@Override
public String getDisplayName() {
return "Jabber Notification";
}
@Override
public String getPluginDescription() {
return "Jabber plugin";
}
/**
* {@inheritDoc}
*/
@Override
public boolean isEnabled() {
return Boolean.TRUE.equals(this.enabled);
}
@Override
public String getHostname()
{
return this.hostname;
}
/**
* Returns the real host to use.
* I.e. when hostname is set returns hostname.
* Otherwise returns {@link #getServiceName()}
*/
@Override
public String getHost() {
if (StringUtils.isNotBlank(this.hostname)) {
return this.hostname;
} else {
return getServiceName();
}
}
/**
* Returns the jabber ID.
*
* The jabber ID may have the syntax <user>[@<domain>[/<resource]]
*/
public String getJabberId()
{
return this.hudsonNickname;
}
@Override
public String getPassword()
{
return this.hudsonPassword;
}
public String getGroupChatNickname()
{
return this.groupChatNickname;
}
@Override
public int getPort()
{
return this.port;
}
/**
* Returns the text to be put into the form field.
* If the port is default, leave it empty.
*/
public String getPortString() {
if(port==5222) return null;
else return String.valueOf(port);
}
public boolean isEnableSASL() {
return enableSASL;
}
public boolean isExposePresence() {
return this.exposePresence;
}
public String getSubscriptionMode() {
return this.subscriptionMode;
}
/**
* Gets the whitespace separated list of group chats to join,
* or null if nothing is configured.
*/
public String getInitialGroupChats() {
return Util.fixEmptyAndTrim(this.initialGroupChats);
}
@Override
public String getDefaultIdSuffix() {
return this.defaultIdSuffix;
}
@Override
public String getCommandPrefix() {
return this.commandPrefix;
}
/**
* Creates a new instance of {@link JabberPublisher} from a submitted form.
*/
@Override
public JabberPublisher newInstance(final StaplerRequest req, JSONObject formData) throws FormException
{
Assert.isNotNull(req, "Parameter 'req' must not be null.");
final String t = req.getParameter(JabberPublisherDescriptor.PARAMETERNAME_TARGETS);
final String[] split;
if (t != null) {
split = t.split("\\s");
} else {
split = new String[0];
}
List<IMMessageTarget> targets = new ArrayList<IMMessageTarget>(split.length);
try {
final IMMessageTargetConverter conv = getIMMessageTargetConverter();
for (String fragment : split) {
IMMessageTarget createIMMessageTarget;
createIMMessageTarget = conv.fromString(fragment);
if (createIMMessageTarget != null) {
targets.add(createIMMessageTarget);
}
}
} catch (IMMessageTargetConversionException e) {
throw new FormException("Invalid Jabber address", e, JabberPublisherDescriptor.PARAMETERNAME_TARGETS);
}
String n = req.getParameter(JabberPublisherDescriptor.PARAMETERNAME_STRATEGY);
if (n == null) {
n = PARAMETERVALUE_STRATEGY_DEFAULT;
} else {
boolean foundStrategyValueMatch = false;
for (final String strategyValue : PARAMETERVALUE_STRATEGY_VALUES) {
if (strategyValue.equals(n)) {
foundStrategyValueMatch = true;
break;
}
}
if (! foundStrategyValueMatch) {
n = PARAMETERVALUE_STRATEGY_DEFAULT;
}
}
boolean notifyStart = "on".equals(req.getParameter(PARAMETERNAME_NOTIFY_START));
boolean notifySuspects = "on".equals(req.getParameter(PARAMETERNAME_NOTIFY_SUSPECTS));
boolean notifyCulprits = "on".equals(req.getParameter(PARAMETERNAME_NOTIFY_CULPRITS));
boolean notifyFixers = "on".equals(req.getParameter(PARAMETERNAME_NOTIFY_FIXERS));
boolean notifyUpstream = "on".equals(req.getParameter(PARAMETERNAME_NOTIFY_UPSTREAM_COMMITTERS));
try {
return new JabberPublisher(targets, n, notifyStart, notifySuspects, notifyCulprits,
notifyFixers, notifyUpstream);
} catch (final IMMessageTargetConversionException e) {
throw new FormException(e, JabberPublisherDescriptor.PARAMETERNAME_TARGETS);
}
}
/**
* {@inheritDoc}
*/
@Override
public boolean configure(StaplerRequest req, JSONObject json) throws hudson.model.Descriptor.FormException {
String en = req.getParameter(PARAMETERNAME_ENABLED);
this.enabled = Boolean.valueOf(en != null);
this.exposePresence = req.getParameter(JabberPublisherDescriptor.PARAMETERNAME_PRESENCE) != null;
this.enableSASL = req.getParameter(JabberPublisherDescriptor.PARAMETERNAME_SASL) != null;
this.subscriptionMode = Util.fixEmptyAndTrim(req.getParameter(JabberPublisherDescriptor.PARAMETERNAME_SUBSCRIPTION_MODE));
applyHostname(req, this.enabled);
applyPort(req, this.enabled);
applyNickname(req, this.enabled);
applyPassword(req, this.enabled);
applyGroupChatNickname(req);
applyInitialGroupChats(req);
applyCommandPrefix(req);
applyDefaultIdSuffix(req);
applyHudsonLoginPassword(req);
if (isEnabled()) {
try {
JabberIMConnectionProvider.setDesc(this);
JabberIMConnectionProvider.getInstance().currentConnection();
} catch (final Exception e) {
//throw new FormException("Unable to create Client: " + ExceptionHelper.dump(e), null);
LOGGER.warning(ExceptionHelper.dump(e));
}
} else {
JabberIMConnectionProvider.getInstance().releaseConnection();
try {
JabberIMConnectionProvider.setDesc(null);
} catch (IMException e) {
// ignore
LOGGER.info(ExceptionHelper.dump(e));
}
LOGGER.info("No hostname specified.");
}
save();
return super.configure(req, json);
}
public FormValidation doJabberIdCheck(@QueryParameter String jabberId,
@QueryParameter final String hostname, @QueryParameter final String port) {
if(!Hudson.getInstance().hasPermission(Hudson.ADMINISTER)) {
return FormValidation.ok();
}
if (jabberId == null || jabberId.trim().length() == 0) {
return FormValidation.error("Jabber ID must not be empty!");
} else if (Util.fixEmptyAndTrim(hostname) != null) {
// validation has already been done for the hostname field
return FormValidation.ok();
} else if (JabberUtil.getDomainPart(jabberId) != null) {
String host = JabberUtil.getDomainPart(jabberId);
try {
checkHostAccessibility(host, port);
return FormValidation.ok();
} catch (UnknownHostException e) {
return FormValidation.error("Unknown host " + host);
} catch (NumberFormatException e) {
return FormValidation.error("Invalid port " + port);
} catch (IOException e) {
return FormValidation.error("Unable to connect to "+hostname+":"+port+" : "+e.getMessage());
}
} else {
return FormValidation.error("No hostname specified - neither via 'Jabber ID' nor via 'Server'!");
}
}
/**
* Validates the connection information.
*/
public FormValidation doServerCheck(@QueryParameter final String hostname,
@QueryParameter final String port) {
if(!Hudson.getInstance().hasPermission(Hudson.ADMINISTER)) {
return FormValidation.ok();
}
String host = Util.fixEmptyAndTrim(hostname);
String p = Util.fixEmptyAndTrim(port);
if (host == null) {
return FormValidation.ok();
} else {
try {
checkHostAccessibility(host, port);
return FormValidation.ok();
} catch (UnknownHostException e) {
return FormValidation.error("Unknown host " + host);
} catch (NumberFormatException e) {
return FormValidation.error("Invalid port " + port);
} catch (IOException e) {
return FormValidation.error("Unable to connect to "+hostname+":"+p+" : "+e.getMessage());
}
}
}
private static void checkHostAccessibility(String hostname, String port)
throws UnknownHostException, IOException, NumberFormatException {
hostname = Util.fixEmptyAndTrim(hostname);
port = Util.fixEmptyAndTrim(port);
int iPort = DEFAULT_PORT;
InetAddress address = InetAddress.getByName(hostname);
if (port != null) {
iPort = Integer.parseInt(port);
}
Socket s = new Socket(address, iPort);
s.close();
}
/**
* {@inheritDoc}
*/
@Override
@SuppressWarnings("unchecked")
public boolean isApplicable(Class<? extends AbstractProject> jobType) {
return true;
}
/**
* Returns the 'user' part of the Jabber ID. E.g. returns
* 'john.doe' for 'john.doe@gmail.com' or
* 'alfred.e.neumann' for 'alfred.e.neumann'.
*/
@Override
public String getUserName() {
return JabberUtil.getUserPart(getJabberId());
}
/**
* Returns 'gmail.com' portion of the nick name 'john.doe@gmail.com', or
* null if not found.
*/
String getServiceName() {
return JabberUtil.getDomainPart(getJabberId());
}
@Override
public String getHudsonPassword() {
return this.hudsonCiPassword;
}
@Override
public String getHudsonUserName() {
return this.hudsonCiLogin;
}
@Override
public IMMessageTargetConverter getIMMessageTargetConverter() {
return JabberPublisher.CONVERTER;
}
@Override
public List<IMMessageTarget> getDefaultTargets() {
// not implemented for Jabber bot
return Collections.emptyList();
}
}