/* * 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(); } }