Package com.streamreduce.core.service

Source Code of com.streamreduce.core.service.ConnectionServiceImpl

/*
* 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.service;

import com.google.common.base.Predicate;
import com.google.common.collect.Iterables;
import com.google.common.collect.Lists;
import com.streamreduce.ConnectionNotFoundException;
import com.streamreduce.ProviderIdConstants;
import com.streamreduce.connections.AuthType;
import com.streamreduce.connections.ConnectionProviderFactory;
import com.streamreduce.connections.ExternalIntegrationConnectionProvider;
import com.streamreduce.connections.OAuthEnabledConnectionProvider;
import com.streamreduce.core.dao.ConnectionDAO;
import com.streamreduce.core.event.EventId;
import com.streamreduce.core.model.Account;
import com.streamreduce.core.model.Connection;
import com.streamreduce.core.model.ConnectionCredentials;
import com.streamreduce.core.model.ConnectionCredentialsEncrypter;
import com.streamreduce.core.model.Event;
import com.streamreduce.core.model.InventoryItem;
import com.streamreduce.core.model.OutboundConfiguration;
import com.streamreduce.core.model.SobaObject;
import com.streamreduce.core.model.User;
import com.streamreduce.core.model.messages.MessageType;
import com.streamreduce.core.service.exception.ConnectionExistsException;
import com.streamreduce.core.service.exception.InvalidCredentialsException;
import com.streamreduce.util.AWSClient;
import com.streamreduce.util.ExternalIntegrationClient;
import com.streamreduce.util.HashtagUtil;
import com.streamreduce.util.InvalidOutboundConfigurationException;
import com.streamreduce.util.SecurityUtil;
import com.streamreduce.util.WebHDFSClient;

import java.io.IOException;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.*;

import javax.annotation.Nullable;

import org.apache.commons.collections.CollectionUtils;
import org.bson.types.ObjectId;
import org.scribe.model.Token;
import org.scribe.model.Verifier;
import org.scribe.oauth.OAuthService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.util.StringUtils;


/**
* Implementation of {@link ConnectionService}.
*/
@Service("connectionService")
public class ConnectionServiceImpl implements ConnectionService {

    protected transient Logger logger = LoggerFactory.getLogger(getClass());

    @Autowired
    private ConnectionDAO connectionDAO;
    @Autowired
    private ConnectionProviderFactory connectionProviderFactory;
    @Autowired
    private InventoryService inventoryService;
    @Autowired
    private MessageService messageService;
    @Autowired
    private EventService eventService;
    @Autowired
    private OAuthTokenCacheService cacheService;

    /**
     * {@inheritDoc}
     */
    @Override
    public Connection createConnection(Connection connection)
            throws ConnectionExistsException, InvalidCredentialsException, IOException {

        addDefaultProtocolToURLIfMissing(connection);
        checkForDuplicate(connection);
        setCredentialsIfOauth(connection);
        setCredentialsIfGateway(connection);
        validateExternalIntegrationConnections(connection);
        validateOutboundConfigurations(connection.getOutboundConfigurations());

        // Add provider id to hashtags just in case it wasn't done already
        String providerId = connection.getProviderId();
        connection.addHashtag(providerId);

        connectionDAO.save(connection);

        decryptCredentials(connection);

        // Create the event stream entry
        Event event = eventService.createEvent(EventId.CREATE, connection, null);

        // Create message
        messageService.sendConnectionMessage(event, connection);

        return connection;
    }

    private void decryptCredentials(Connection connection) {
        ConnectionCredentialsEncrypter credentialsEncrypter = new ConnectionCredentialsEncrypter();
        // force decryption until we upgrade to morphia 1.0
        if (connection.getCredentials() != null) {
            credentialsEncrypter.decrypt(connection.getCredentials());
        }
        if (CollectionUtils.isNotEmpty(connection.getOutboundConfigurations())) {
            for (OutboundConfiguration outboundConfiguration : connection.getOutboundConfigurations()) {
                if (outboundConfiguration.getCredentials() != null) {
                    credentialsEncrypter.decrypt(outboundConfiguration.getCredentials());
                }
            }
        }
    }

    private void addDefaultProtocolToURLIfMissing(Connection connection) {
        // For Feed connections, hold the user's hand by putting the default protocol (http://) on the URL if not there
        if (connection.getProviderId().equals(ProviderIdConstants.FEED_PROVIDER_ID)) {
            // Add the http:// protocol if missing
            try {
                new URL(connection.getUrl());
            } catch (MalformedURLException e) {
                if (connection.getUrl() != null && !connection.getUrl().contains("://")) {
                    connection.setUrl("http://" + connection.getUrl().trim());
                }
            }
        }
    }

    /**
     * Initializes values in connection.credentials for Oauth connections. There are three possible things that
     * can happen (not taking exceptions into account).
     * <p/>
     * <ol>
     * <li>If connection.authType isn't OAUTH, this method returns immediately without mutating anything in
     * connection</li>
     * <li>If connection.credentials.verifier is null/blank, we assume that the credentials.oauthToken
     * and credentials.oauthTokenSecret fields were previously valid and are meant to be re-used.</li>
     * <li>If connection.credentials.verifier had content, we assume that we are in the last steps of
     * an OAuth handshake with the connection provider and real credentials will retrieved using that
     * verification field.</li>
     * </ol>
     *
     * @param connection A connection currently being created.
     */
    private void setCredentialsIfOauth(Connection connection) {
        if (connection.getAuthType() != AuthType.OAUTH) {
            return;
        }

        if (StringUtils.hasText(connection.getCredentials().getOauthVerifier())) {
            finishHandshakeAndSetRealOauthTokens(connection);
        }

        setIdentityForOauthConnection(connection);
    }

    private void finishHandshakeAndSetRealOauthTokens(Connection connection) {
        ConnectionCredentials credentials = connection.getCredentials();
        OAuthEnabledConnectionProvider oauthProvider =
                connectionProviderFactory.oauthEnabledConnectionProviderFromId(connection.getProviderId());
        OAuthService oAuthService = oauthProvider.getOAuthService();
        Token requestToken = cacheService.retrieveAndRemoveToken(credentials.getOauthToken());
        Token token = oAuthService.getAccessToken(requestToken, new Verifier(credentials.getOauthVerifier()));
        oauthProvider.updateCredentials(credentials, token);
    }

    private void setIdentityForOauthConnection(Connection connection) {
        OAuthEnabledConnectionProvider oauthProvider =
                connectionProviderFactory.oauthEnabledConnectionProviderFromId(connection.getProviderId());
        connection.getCredentials().setIdentity(oauthProvider.getIdentityFromProvider(connection));
    }

    private void setCredentialsIfGateway(Connection connection) {
        if (connection.getAuthType() != null && connection.getAuthType().equals(AuthType.API_KEY)) {
            // auto generate an API key for them
            if (connection.getCredentials() == null) {
                connection.setCredentials(new ConnectionCredentials());
            }
            // we also use a user agent as a validation factor
            // so when we later validate the token, we also validate the user agent
            String apiToken = SecurityUtil.issueRandomAPIToken();
            connection.getCredentials().setIdentity(apiToken);
        }
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public List<Connection> getConnections(@Nullable String type) {
        // TODO: Event handling
        return connectionDAO.allConnectionsOfType(type);
    }

    @Override
    public List<Connection> getPublicConnections(@Nullable String type) {
        return connectionDAO.allPublicConnectionsOfType(type);
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public List<Connection> getConnections(@Nullable String type, User user) {
        // TODO: Event handling
        return connectionDAO.forTypeAndUser(type, user);
    }


    @Override
    public List<Connection> getAccountConnections(Account account) {
        return connectionDAO.forAccount(account);
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public Connection getConnection(ObjectId id) throws ConnectionNotFoundException {
        Connection connection = connectionDAO.get(id);

        if (connection == null) {
            // TODO: Event handling
            throw new ConnectionNotFoundException(id == null ? "null" : id.toString());
        }

        return connection;
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public Connection updateConnection(Connection connection) throws ConnectionExistsException,
            InvalidCredentialsException, IOException {
        return updateConnection(connection, false);
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public Connection updateConnection(Connection connection, boolean silentUpdate) throws ConnectionExistsException,
            InvalidCredentialsException, IOException {

        connection.setSilentUpdate(silentUpdate);

        if (!silentUpdate) {
            checkForDuplicate(connection);
            validateExternalIntegrationConnections(connection);
            validateOutboundConfigurations(connection.getOutboundConfigurations());
        }

        connectionDAO.save(connection);

        decryptCredentials(connection);

        if (!silentUpdate) {
            // Create the event stream entry
            Event event = eventService.createEvent(EventId.UPDATE, connection, null);

            // Create message
            messageService.sendConnectionMessage(event, connection);
        }

        return connection;
    }

    private void validateExternalIntegrationConnections(Connection connection) throws InvalidCredentialsException, IOException {
        ExternalIntegrationConnectionProvider connectionProvider;
        try {
            connectionProvider = connectionProviderFactory.externalIntegrationConnectionProviderFromId(connection.getProviderId());
        } catch (Exception e) {
            logger.info("Connection with providerId of " + connection.getProviderId() + " does not " +
                    "support validation via an external client.");
            return;
        }

        ExternalIntegrationClient externalClient = connectionProvider.getClient(connection);
        externalClient.validateConnection();
        externalClient.cleanUp();
    }

    private void validateOutboundConfigurations(Set<OutboundConfiguration> outboundConfigurations) throws InvalidCredentialsException, IOException {
        if (CollectionUtils.isEmpty(outboundConfigurations)) {
            return;
        }

        ExternalIntegrationClient externalClient = null;
        try {
            for (OutboundConfiguration outboundConfiguration : outboundConfigurations) {
                if (outboundConfiguration.getProtocol().equals("s3")) {
                    AWSClient awsClient = new AWSClient(outboundConfiguration);
                    externalClient = awsClient;
                    externalClient.validateConnection();
                    try {
                        awsClient.createBucket(outboundConfiguration);
                    } catch (IllegalStateException e) { //thrown when a bucket name is already taken
                        throw new InvalidOutboundConfigurationException(e.getMessage(), e);
                    }
                } else if (outboundConfiguration.getProtocol().equals("webhdfs")) {
                    externalClient = new WebHDFSClient(outboundConfiguration);
                    externalClient.validateConnection();
                }
            }
        } finally {
            if (externalClient != null) {
                externalClient.cleanUp();
            }
        }
    }

    public void deleteConnection(Connection connection) {
        deleteConnectionInventory(connection.getId());

        Event event = eventService.createEvent(EventId.DELETE, connection, null);
        messageService.sendConnectionMessage(event, connection);

        connectionDAO.delete(connection);
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public void deleteConnectionInventory(ObjectId connectionId) {
        List<InventoryItem> inventoryItems = inventoryService.getInventoryItems(connectionId);

        for (InventoryItem inventoryItem : inventoryItems) {
            inventoryService.deleteInventoryItem(inventoryItem);
        }
    }

    /**
     * Checks a given connection against all existing connections in a given account to ensure a duplicate connection
     * does not already exist.  A connection is considered a duplicate of another connection when
     * <ul>
     * <li>Aliases are the same</li>
     * <li>If there are credentials with the connection, url+credentials must not be the same</li>
     * <li>If there are no credentials with the connection, url must be not be the same</li>
     * </ul>
     *
     * @param connection a potential new/updated Connection
     * @throws ConnectionExistsException when a duplicate connection is detected.
     */
    protected void checkForDuplicate(Connection connection) throws ConnectionExistsException {
        List<Connection> connections = connectionDAO.forTypeAndUser(connection.getType(), connection.getUser());

        for (Connection otherConnection : connections) {
            // We use this for create and update so id might not be set yet
            try {
                // If the connection being compared against is the same as the one being updated, do not compare
                if (getConnection(connection.getId()) != null && otherConnection.getId().equals(connection.getId())) {
                    continue;
                }
            } catch (ConnectionNotFoundException e) {
                // Should not matter in this context
            }

            checkForEqualAlias(connection, otherConnection);

            String cUrl = connection.getUrl() != null ? connection.getUrl().trim().toLowerCase() : "";
            String oUrl = otherConnection.getUrl() != null ? otherConnection.getUrl().trim().toLowerCase() : "";

            // Duplicate if credentials aren't set and URL exists elsewhere in account.
            if (credsAreNullOrBlank(connection) && credsAreNullOrBlank(otherConnection) && cUrl.equals(oUrl)) {
                throw ConnectionExistsException.Factory.duplicateCredentials(connection);
            }

            // Duplicate if both credentials and URLs are equal.
            // If the URLs are the same then make sure credentials are not
            if (cUrl.equals(oUrl)) {
                ConnectionCredentials cCreds = connection.getCredentials();
                ConnectionCredentials oCreds = otherConnection.getCredentials();

                if ((cCreds == null && oCreds == null) || (cCreds != null && oCreds != null && cCreds.equals(oCreds))) {
                    throw ConnectionExistsException.Factory.duplicateCredentials(connection);
                }
            }
        }
    }

    private boolean credsAreNullOrBlank(Connection connection) {
        return connection.getCredentials() == null || connection.getCredentials().getIdentity() == null ||
                connection.getCredentials().getIdentity().trim().equals("");
    }

    private void checkForEqualAlias(Connection newConnection, Connection existingConnection)
            throws ConnectionExistsException {
        if (newConnection.getAlias() != null && existingConnection.getAlias() != null &&
                newConnection.getAlias().equalsIgnoreCase(existingConnection.getAlias())) {
            throw ConnectionExistsException.Factory.duplicateAlias(newConnection);
        }
    }

    @Override
    public void fireOneTimeHighPriorityJobForConnection(Connection connection) throws ConnectionNotFoundException,
            InvalidCredentialsException, IOException {
        inventoryService.refreshInventoryItemCache(connection);
        inventoryService.pullInventoryItemActivity(connection);
    }

    @Override
    public void addHashtag(Connection target, SobaObject tagger, String tag) {
        target.addHashtag(tag);

        handleHashtagEvent(EventId.HASHTAG_ADD, target, tagger, tag);

        try {
            updateConnection(target, true);
        } catch (ConnectionExistsException | IOException | InvalidCredentialsException e) {
            logger.error(e.getMessage());
        }
    }

    @Override
    public void removeHashtag(Connection target, SobaObject tagger, String tag) {
        String normalizedTag = HashtagUtil.normalizeTag(tag);
        target.removeHashtag(normalizedTag);

        handleHashtagEvent(EventId.HASHTAG_DELETE, target, tagger, normalizedTag);

        try {
            updateConnection(target, true);
        } catch (ConnectionExistsException | IOException | InvalidCredentialsException e) {
            logger.error(e.getMessage());
        }
    }

    private void handleHashtagEvent(EventId eventId, Connection target, SobaObject tagger, String tag) {
        // Create the event
        Map<String, Object> eventContext = new HashMap<>();

        if (eventId == EventId.HASHTAG_ADD) {
            eventContext.put("addedHashtag", tag);
        } else if (eventId == EventId.HASHTAG_DELETE) {
            eventContext.put("deletedHashtag", tag);
        }

        Event event = eventService.createEvent(eventId, target, eventContext);

        // Create the message
        // TODO: Should this use MessageService#sendConnectionMessage(Event, Connection)?
        messageService.sendAccountMessage(event, tagger, target, new Date().getTime(),
                MessageType.CONNECTION, target.getHashtags(), null);
    }

    @Override
    public List<Connection> getConnectionsByExternalId(String externalId, final User user) {
        List<Connection> connections = connectionDAO.getByExternalId(externalId);
        return Lists.newArrayList(Iterables.filter(connections, new Predicate<Connection>() {
            @Override
            public boolean apply(@Nullable Connection connection) {
                return (connection != null && connection.getUser().equals(user));
            }
        }));
    }
}
TOP

Related Classes of com.streamreduce.core.service.ConnectionServiceImpl

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.