/*
* Copyright 2012 Nodeable Inc
*
* Licensed 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 com.streamreduce.core.model;
import com.google.code.morphia.annotations.Embedded;
import com.google.code.morphia.annotations.Entity;
import com.google.code.morphia.annotations.PostLoad;
import com.google.code.morphia.annotations.PrePersist;
import com.google.common.collect.Sets;
import com.streamreduce.ProviderIdConstants;
import com.streamreduce.connections.AuthType;
import com.streamreduce.connections.ConnectionProvider;
import com.streamreduce.core.validation.ValidConnectionProviderId;
import com.streamreduce.core.validation.ValidConnectionType;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import javax.validation.Valid;
import javax.validation.constraints.NotNull;
import net.sf.json.JSONObject;
import org.apache.commons.collections.CollectionUtils;
import org.apache.commons.lang.StringUtils;
import org.apache.commons.lang.builder.EqualsBuilder;
import org.apache.commons.lang.builder.HashCodeBuilder;
import org.hibernate.validator.constraints.NotEmpty;
import org.hibernate.validator.constraints.URL;
import static com.streamreduce.ProviderIdToTypeMapping.PROVIDER_IDS_TO_TYPES_MAP;
@Entity(value = "connections", noClassnameStored = true)
public class Connection extends AbstractActivityPollingSobaObject {
private static final long serialVersionUID = -2385566052567775030L;
@NotEmpty
@ValidConnectionProviderId
private String providerId;
@NotEmpty
@ValidConnectionType
private String type; // TODO: why is this a String and not enum?
@URL
private String url;
@Embedded
@Valid
private ConnectionCredentials credentials;
@NotNull
private AuthType authType;
private Set<OutboundConfiguration> outboundConfigurations;
private boolean pollingInProgress;
private long pollingLastExecutionTime;
private long pollingFailedCount;
private boolean disabled = false;
// @Embedded
// private APIAuthenticationToken authenticationToken; // TODO: for IMG?
// All Connection instantiation must occur through Connection.Builder
private Connection() {
}
/**
* <p>An identifying string value that represents the provider. An example of a providerId might be "github", or "jira".
* A providerId must correspond to a type of connection as well. For instance, a providerId of "github" implies
* a type of "projecthosting".</p>
* <p>All eligible values for ProviderId can be found in {@link com.streamreduce.ProviderIdConstants}.</p>
*
* @return String representing the provider id.
*/
public String getProviderId() {
return providerId;
}
public void setProviderId(String providerId) {
if (providerId == null) {
throw new IllegalArgumentException("providerId can't be null");
}
this.providerId = providerId;
}
/**
* A string that represents the classification of the connection. Examples of type might be "cloud" or
* "projecthosting".
*
* @return String representing the type.
*/
public String getType() {
return type;
}
public void setType(String type) {
if (type == null) {
throw new IllegalArgumentException("type can't be null");
}
this.type = type;
}
/**
* A (base) Url used as a location for the Connection. Some connection providers may have a static or default URL
* and as such this field may be null and not used or required.
*
* @return A string representing the (base) Url for the Connection.
*/
public String getUrl() {
return url;
}
public void setUrl(String url) {
//Validity of url will be determined by @URL validator on the url field.
this.url = StringUtils.trim(url);
}
/**
* @return Returns a instance of {@link ConnectionCredentials} used to authenticate to the Connection.
*/
public ConnectionCredentials getCredentials() {
return credentials;
}
public void setCredentials(ConnectionCredentials credentials) {
this.credentials = credentials;
}
/**
* Represents the type of authentication used to auth to the Connection.
*
* @return an {@link AuthType}
*/
public AuthType getAuthType() {
return authType;
}
public void setAuthType(AuthType authType) {
if (authType == null) {
throw new IllegalArgumentException("authType can't be null");
}
this.authType = authType;
}
/**
* A Set of {@link OutboundConfiguration} describing where messages generated for this Connection are sent to.
*
* @return A Set of OutboundConfigurations for this connection.
*/
public Set<OutboundConfiguration> getOutboundConfigurations() {
if (outboundConfigurations == null) {
this.outboundConfigurations = new HashSet<>();
}
return this.outboundConfigurations;
}
public void setOutboundConfigurations(Set<OutboundConfiguration> outboundConfigurations) {
HashSet<OutboundConfiguration> filteredOutboundConfigs = new HashSet<>();
if (!CollectionUtils.isEmpty(outboundConfigurations)) {
for (OutboundConfiguration outboundConfiguration : outboundConfigurations) {
if (outboundConfiguration != null) {
outboundConfiguration.setOriginatingConnection(this);
filteredOutboundConfigs.add(outboundConfiguration);
}
}
}
this.outboundConfigurations = filteredOutboundConfigs;
}
/**
* Adds an outbound configuration to the existing set of configs.
*
* @param outboundConfiguration outbound configuration to associate with this connection.
*/
public void addOutboundConfiguration(OutboundConfiguration outboundConfiguration) {
if (outboundConfiguration != null) {
outboundConfiguration.setOriginatingConnection(this);
getOutboundConfigurations().add(outboundConfiguration);
}
}
/**
* Returns true if this connection has been disabled (likely by an admin).
*
* @return boolean
*/
public boolean isDisabled() {
return disabled;
}
public void setDisabled(boolean disabled) {
this.disabled = disabled;
}
public boolean isPollingInProgress() {
return pollingInProgress;
}
public void setPollingInProgress(boolean pollingInProgress) {
this.pollingInProgress = pollingInProgress;
}
public long getPollingLastExecutionTime() {
return pollingLastExecutionTime;
}
public void setPollingLastExecutionTime(long pollingLastExecutionTime) {
this.pollingLastExecutionTime = pollingLastExecutionTime;
}
public long getPollingFailedCount() {
return pollingFailedCount;
}
public void setPollingFailedCount(long pollingFailedCount) {
this.pollingFailedCount = pollingFailedCount;
}
/**
* Merges the following properties from a JSON object in to the Connection object:
* <ul>
* <li>credentials</li>
* <li>authType</li>
* <li>alias</li>
* <li>description</li>
* <li>visibility</li>
* <li>url</li>
* </ul>
*
* @param json {@link JSONObject} used to defined properties of this Connection.
*/
@Override
public void mergeWithJSON(JSONObject json) {
super.mergeWithJSON(json);
// Allow changing credentials
if (json.containsKey("credentials")) {
JSONObject rawNewCredentials = json.getJSONObject("credentials");
// If rawNewCredentials is empty, we risk clobbering the old credentials. Which is bad.
if (!rawNewCredentials.isEmpty()) {
ConnectionCredentials newCredentials = new ConnectionCredentials();
if (rawNewCredentials.containsKey("identity") && rawNewCredentials.getString("identity") != null) {
newCredentials.setIdentity(rawNewCredentials.getString("identity"));
}
if (rawNewCredentials.containsKey("credential") && rawNewCredentials.getString("credential") != null) {
newCredentials.setCredential(rawNewCredentials.getString("credential"));
}
if (rawNewCredentials.containsKey("api_key") && rawNewCredentials.getString("api_key") != null) {
newCredentials.setApiKey(rawNewCredentials.getString("api_key"));
}
if (rawNewCredentials.containsKey("oauthToken") && rawNewCredentials.getString("oauthToken") != null) {
newCredentials.setOauthToken(rawNewCredentials.getString("oauthToken"));
}
if (rawNewCredentials.containsKey("oauthTokenSecret") && rawNewCredentials.getString("oauthTokenSecret") != null) {
newCredentials.setOauthTokenSecret(rawNewCredentials.getString("oauthTokenSecret"));
}
if (rawNewCredentials.containsKey("oauthVerifier") && rawNewCredentials.getString("oauthVerifier") != null) {
newCredentials.setOauthVerifier(rawNewCredentials.getString("oauthVerifier"));
}
setCredentials(newCredentials);
}
}
if (json.containsKey("url")) {
setUrl(StringUtils.trim(json.getString("url")));
}
if (json.containsKey("authType")) {
setAuthType(AuthType.valueOf(json.getString("authType")));
}
}
/* Extending this class is disabled on purpose. If you need to extend the builder, please look at
InventoryItem.Builder to see how to do it properly.
*/
public static final class Builder extends SobaObject.Builder<Connection, Builder> {
public Builder() {
super(new Connection());
}
public Builder(JSONObject json) {
super(new Connection());
theObject.mergeWithJSON(json);
if (json.containsKey("providerId")) {
theObject.providerId = json.getString("providerId");
theObject.type = PROVIDER_IDS_TO_TYPES_MAP.get(theObject.providerId);
}
}
/**
* Sets both the provider and type of this connection from the supplied {@link ConnectionProvider}
*
* @param provider ConnectionProvider that represents the provider for this Connection.
* @return The {@link Builder} currently being built up.
*/
public Builder provider(ConnectionProvider provider) {
if (isBuilt) {
throw new IllegalStateException("The object cannot be modified after built");
}
theObject.setProviderId(provider.getId());
// autotag based on the provider
theObject.addHashtag(provider.getId());
theObject.setType(provider.getType());
return this;
}
public Builder authType(AuthType authType) {
if (isBuilt) {
throw new IllegalStateException("The object cannot be modified after built");
}
theObject.setAuthType(authType);
return this;
}
public Builder url(String url) {
if (isBuilt) {
throw new IllegalStateException("The object cannot be modified after built");
}
theObject.setUrl(StringUtils.trim(url));
return this;
}
public Builder credentials(ConnectionCredentials credentials) {
if (isBuilt) {
throw new IllegalStateException("The object cannot be modified after built");
}
theObject.setCredentials(credentials);
return this;
}
public Builder mergeWithJSON(JSONObject jsonObject) {
if (isBuilt) {
throw new IllegalStateException("The object cannot be modified after built");
}
theObject.mergeWithJSON(jsonObject);
return this;
}
public Builder metadata(Map<String, String> metadata) {
if (isBuilt) {
throw new IllegalStateException("The object cannot be modified after built");
}
theObject.setMetadata(metadata);
return this;
}
public Builder outboundConfigurations(OutboundConfiguration... outboundConfigurations) {
Set<OutboundConfiguration> outboundConfigurationSet = Sets.newHashSet(outboundConfigurations);
if (outboundConfigurationSet.contains(null)) {
outboundConfigurationSet.remove(null);
}
theObject.setOutboundConfigurations(outboundConfigurationSet);
return this;
}
@Override
public Connection build() {
if (theObject.getAuthType() == null) {
throw new IllegalStateException("Connection object must have an authType set.");
}
sanitizeProperties();
addInboundConnectionOnOutboundConfigurations();
// TODO: if IMG create an API key
return super.build();
}
/**
* Adds a reference to theObject being built as the inboundConnection property on all of theObject's
* OutboundConnections
*/
private void addInboundConnectionOnOutboundConfigurations() {
if (theObject.outboundConfigurations != null) {
for (OutboundConfiguration outboundConfiguration : theObject.outboundConfigurations) {
outboundConfiguration.setOriginatingConnection(theObject);
}
}
}
/**
* Ensure that properties on the Connection used for comparison aren't created with leading/trailing
* whitespace.
*/
private void sanitizeProperties() {
theObject.setUrl(theObject.getUrl() != null ? theObject.getUrl().trim() : null);
theObject.setAlias(theObject.getAlias() != null ? theObject.getAlias().trim() : null);
}
/**
* {@inheritDoc}
*/
@Override
public Builder getRealBuilder() {
return this;
}
}
@PrePersist
@SuppressWarnings("unused")
//Used by Morphia
void setDefaultAuthTypeIfMissing() {
if (authType != null) {
return;
}
//Set default authType if one isn't set already for any providers that existed prior to
//authType being introduced
if (this.getProviderId().equals(ProviderIdConstants.JIRA_PROVIDER_ID)) {
authType = AuthType.USERNAME_PASSWORD;
} else if (this.getProviderId().equals(ProviderIdConstants.GITHUB_PROVIDER_ID)) {
authType = AuthType.USERNAME_PASSWORD;
} else if (this.getProviderId().equals(ProviderIdConstants.AWS_PROVIDER_ID)) {
authType = AuthType.USERNAME_PASSWORD;
} else if (this.getProviderId().equals(ProviderIdConstants.FEED_PROVIDER_ID)) {
authType = AuthType.NONE;
} else {
throw new IllegalStateException("Connection cannot be persisted. It has a ProviderType without an" +
" established default AuthType");
}
}
@PostLoad
@SuppressWarnings("unused")
//Used by morphia
void setAllOutboundConnectionsToReferenceThisAsInbound() {
if (CollectionUtils.isNotEmpty(outboundConfigurations)) {
for (OutboundConfiguration outboundConfiguration : outboundConfigurations) {
outboundConfiguration.setOriginatingConnection(this);
}
}
}
@Override
public boolean equals(Object o) {
if (o instanceof Connection) {
Connection that = (Connection) o;
return new EqualsBuilder()
.append(this.providerId, that.providerId)
.append(this.type, that.type)
.append(this.url, that.url)
.append(this.credentials, that.credentials)
.append(this.authType, that.authType)
.append(this.alias, that.alias)
.append(this.description, that.description)
.append(this.account, that.account)
.append(this.user, that.user)
.append(this.visibility, that.visibility)
.append(this.hashtags, that.hashtags)
.append(this.id, that.id)
.append(this.outboundConfigurations, that.outboundConfigurations)
.isEquals();
}
return false;
}
@Override
public int hashCode() {
return new HashCodeBuilder()
.append(this.providerId)
.append(this.type)
.append(this.url)
.append(this.credentials)
.append(this.authType)
.append(this.alias)
.append(this.description)
.append(this.account)
.append(this.user)
.append(this.visibility)
.append(this.hashtags)
.append(this.id)
.append(this.outboundConfigurations)
.toHashCode();
}
}