/* * 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.rest.resource.api; import com.google.common.collect.ImmutableMap; import com.streamreduce.ConnectionNotFoundException; import com.streamreduce.ConnectionTypeConstants; import com.streamreduce.ProviderIdConstants; import com.streamreduce.connections.ConnectionProvider; import com.streamreduce.connections.ConnectionProviderFactory; import com.streamreduce.core.model.*; import com.streamreduce.core.service.ConnectionService; import com.streamreduce.core.service.InventoryService; import com.streamreduce.core.service.exception.ConnectionExistsException; import com.streamreduce.core.service.exception.InvalidCredentialsException; import com.streamreduce.rest.dto.response.*; import com.streamreduce.rest.resource.ErrorMessages; import com.streamreduce.util.AbstractProjectHostingClient; import com.streamreduce.util.ConnectionUtils; import com.streamreduce.util.JiraClient; import com.wordnik.swagger.annotations.Api; import com.wordnik.swagger.annotations.ApiOperation; import com.wordnik.swagger.annotations.ApiParam; import net.sf.json.JSONArray; import net.sf.json.JSONObject; import org.apache.commons.lang.StringUtils; import org.bson.types.ObjectId; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; import org.springframework.util.CollectionUtils; import javax.ws.rs.*; import javax.ws.rs.core.MediaType; import javax.ws.rs.core.Response; import javax.xml.soap.SOAPException; import java.io.IOException; import java.net.UnknownHostException; import java.util.*; /** * A Connection includes: a globally unique identifier, a provider and type identifying the provider of an external * integration and the interaction model with that provider, an authentication type that determines how Nodeable will * authenticate to a connection on a user's behalf, an account unique alias, an optional description, hashtags associated * to the connection, and the visibility scope of all messages created from that account. */ @Component @Path("api/connections") @Api(value = "/api/connections", description = "A Connection is an abstraction that provides a top level " + "description of how Nodeable will connect to external providers on behalf of users." ) public class ConnectionResource extends AbstractOwnableResource { @Autowired ConnectionProviderFactory connectionProviderFactory; @Autowired ConnectionService connectionService; @GET @Path("/types") @ApiOperation(value = "Returns all connection types available.", notes = "An array of string value is returned representing all possible connection types. " + "A connector type is a top level identifier for distinguishing what the type of a connection " + "(e.g. cloud, projecthosting). Every connection has a top level type.") public Response getProviderTypes() { return Response.ok(ConnectionUtils.PROVIDER_MAP.keySet()).build(); } @GET @Path("/providers") @ApiOperation(value = "Returns all connection providers available.", notes = "Returns an array of strings listing of connection providers. Each individual connection " + "describes an identifier for a provider, canonical names for each provider, the type (e.g. cloud" + ", projecthosting) that the provider belongs to, and optionally all possible methods of " + "authentication for a given provider. Every connection has a provider.", responseClass = "com.streamreduce.rest.dto.response.ConnectionProvidersResponseDTO") public Response getProviderList(@QueryParam("showAuthTypes") @ApiParam(name = "showAuthTypes", value = "false", defaultValue = "false") boolean showAuthTypes) { ConnectionProvidersResponseDTO responseDTO = new ConnectionProvidersResponseDTO(); List<ConnectionProviderResponseDTO> providers = new ArrayList<>(); for (ConnectionProvider provider : ConnectionUtils.getAllProviders()) { providers.add(ConnectionProviderResponseDTO.toDTO(provider, showAuthTypes)); } responseDTO.setProviders(providers); return Response.ok(responseDTO).build(); } /** * Supported connection provider types that can be specified are: cloud, projecthosting, feed, gateway * * @param providerType the provider type to filter all connection providers by * @param showAuthTypes boolean value specifying whether authType details should be included or not * @return the list of connection providers * @resource.representation.200.doc returned when all connection provider types are successfully returned * @response.representation.400.doc Returned when an invalid or non-existent providerType is supplied. */ @GET @Path("providers/{providerType}") @ApiOperation(value = "Returns a list of connection providers for only the specified type.", notes = "Using any possible value from /connections/types, an array of providers for that type will be " + "returned", responseClass = "com.streamreduce.rest.dto.response.ConnectionProvidersResponseDTO" ) public Response getProviderList(@ApiParam(name = "type", value = "cloud", required = true) @PathParam("type") String providerType, @ApiParam(name = "showAuthTypes", value = "false", defaultValue = "false") @QueryParam("showAuthTypes") boolean showAuthTypes) { // We want our caller to know they requested an invalid connection type if (!ConnectionUtils.PROVIDER_MAP.containsKey(providerType)) { return error("'" + providerType + "' is an invalid connection provider type.", Response.status(Response.Status.BAD_REQUEST)); } ConnectionProvidersResponseDTO responseDTO = new ConnectionProvidersResponseDTO(); List<ConnectionProviderResponseDTO> providers = new ArrayList<>(); for (ConnectionProvider provider : ConnectionUtils.getSupportedProviders(providerType)) { providers.add(ConnectionProviderResponseDTO.toDTO(provider, showAuthTypes)); } responseDTO.setProviders(providers); return Response.ok(responseDTO).build(); } @GET @ApiOperation(value = "Returns all connections that the current user hass access to.", responseClass = "com.streamreduce.rest.dto.response.ConnectionResponseDTO" ) public Response listAllConnections() { List<ConnectionResponseDTO> connectionsDTO = new ArrayList<>(); User user = securityService.getCurrentUser(); List<Connection> connections = connectionService.getConnections(null, user); for (Connection connection : connections) { // do not include blacklisted connections, these have been "deleted". if (!ConnectionUtils.isBlacklisted(user.getAccount(), connection.getId())) { connectionsDTO.add(toFullDTO(connection)); } } return Response.ok(connectionsDTO).build(); } /** * Returns a list of connection providers of a particular type. * Supported connection provider types: cloud, projecthosting, feed, and gateway. * * @param providerType the connection provider type * @return the collection of connections for the given type or an error if something goes wrong * @response.representation.200.doc Returned when a list of connections for a type is successfully rendered */ @GET @Path("/types/{providerType}") @ApiOperation(value = "Returns a list of connections for a given type that the current user has access to", responseClass = "com.streamreduce.rest.dto.response.ConnectionResponseDTO" ) public Response listConnectionsOfType(@ApiParam(name = "type", value = "cloud", required = true) @PathParam("type") String providerType) { List<ConnectionResponseDTO> connectionsDTO = new ArrayList<>(); User user = securityService.getCurrentUser(); List<Connection> connections = connectionService.getConnections(providerType, user); for (Connection connection : connections) { connectionsDTO.add(toFullDTO(connection)); } return Response.ok(connectionsDTO).build(); } @GET @Path("/externalId/{externalId}") @ApiOperation(value = "Returns a list of connections with an externalId matching the externalId in the path that " + "the user has access to.", responseClass = "com.streamreduce.rest.dto.response.ConnectionResponseDTO" ) public Response getConnectionsByExternalId(@PathParam("externalId") @ApiParam(name = "externalId", required = true) String externalId) { List<ConnectionResponseDTO> connectionsDTO = new ArrayList<>(); User user = securityService.getCurrentUser(); List<Connection> connections = connectionService.getConnectionsByExternalId(externalId, user); for (Connection connection : connections) { connectionsDTO.add(toFullDTO(connection)); } return Response.ok(connectionsDTO).build(); } /** * @response.representation.200.doc Returned when a connection for the given id is successfully retrieved * @response.representation.400.doc Returned when the id is null when passed in * @response.representation.404.doc Returned when you request a connection whose id does not exist for the given user */ @GET @Path("{id}") @ApiOperation(value = "Returns a connection with the given id.", responseClass = "com.streamreduce.rest.dto.response.ConnectionResponseDTO" ) public Response getConnectionWithId(@PathParam("id") @ApiParam(name = "id", required = true) String id) { if (StringUtils.isBlank(id)) { return error(ErrorMessages.MISSING_REQUIRED_FIELD, Response.status(Response.Status.BAD_REQUEST)); } ObjectId objectId = new ObjectId(id); try { return Response .ok(toFullDTO(connectionService.getConnection(objectId))) .build(); } catch (ConnectionNotFoundException e) { return error(e.getMessage(), Response.status(Response.Status.NOT_FOUND)); } } /** * @param json the json payload describing the connection * @response.representation.200.doc Returned when a connection is successfully created * @response.representation.400.doc Returned when a duplicate connection (or alias) exists, if credentials are invalid, or if the json payload is missing necessary attributes. * @response.representation.500.doc Returned a host can't be found from a client supplied url or another unexpected network layer problem occurred connection to host. */ @POST @Consumes(MediaType.APPLICATION_JSON) @ApiOperation(value = "Creates a new connection.", notes = "The passed in connection will be checked to make sure it can connect to the specified " + "provider with the optionally supplied url credentials. A new connection must also have an account" + " unique alias.", responseClass = "com.streamreduce.rest.dto.response.ConnectionResponseDTO" ) public Response createConnection(@ApiParam(name = "connection", required = true, value = "A JSON object with " + "all fields for a connection included with proper values") JSONObject json) { User currentUser = securityService.getCurrentUser(); try { Connection connection = new Connection.Builder() .mergeWithJSON(json) .user(currentUser) //sets user and account .provider(connectionProviderFactory.connectionProviderFromId(getJSON(json, "providerId"))) //sets providerId and type .outboundConfigurations(extractOutboundConfigurationsFromJSON(json)) //set outboundConfigurations if available in the jsonObject .build(); connectionService.createConnection(connection); return Response.ok(toFullDTO(connection)).build(); } catch (ConnectionExistsException e) { return error(e.getMessage(), Response.status(Response.Status.BAD_REQUEST)); } catch (ConnectionNotFoundException e) { return error("A Connection specified in an outboundConfiguration does not exist: " + e.getMessage(), Response.status(Response.Status.BAD_REQUEST)); } catch (InvalidCredentialsException e) { return error(e.getMessage(), Response.status(Response.Status.BAD_REQUEST)); } catch (UnknownHostException e) { return error(e.getMessage() + " is an unknown host. ", Response.status(Response.Status.INTERNAL_SERVER_ERROR)); } catch (IOException e) { return error(e.getMessage(), Response.status(Response.Status.INTERNAL_SERVER_ERROR)); } catch (IllegalArgumentException e) { // This happens with null URLs in project hosting validation (pre-DAO persist) ConstraintViolationExceptionResponseDTO dto = new ConstraintViolationExceptionResponseDTO(); dto.setViolations(ImmutableMap.of("url", e.getMessage())); return Response.status(Response.Status.BAD_REQUEST).entity(dto).build(); } catch (RuntimeException e) { //Catch all for runtime exceptions e.printStackTrace(); return error(e.getMessage(), Response.status(Response.Status.BAD_REQUEST)); } } /** * Creates a new external resource on a given connection if the connection provider supports two-way integration. * <p/> * Presently only creation of new issues on connections with a provider type of "jira" is supported. * * @param id the id of the connection to create the external resource * @param json the json payload describing the resource to be created * @return the newly assigned resource id. * @response.representation.200.doc Returned when an external resource on the connection is successfully created * @resource.representation.400.doc Returned when the provider does not support creation of the resource * @resource.representation.404.doc Returned when the id is not found * @resource.representation.500.doc Returned when the resource creation request to external provider failed */ @POST @Path("/{id}") @Consumes(MediaType.APPLICATION_JSON) public Response createExternalResource(@PathParam("id") String id, JSONObject json) { if (StringUtils.isBlank(id)) { return error(ErrorMessages.MISSING_REQUIRED_FIELD, Response.status(Response.Status.BAD_REQUEST)); } ObjectId objectId = new ObjectId(id); AbstractProjectHostingClient projectHostingClient = null; try { Connection connection = connectionService.getConnection(objectId); if (!isOwnerOrAdmin(connection.getUser(), connection.getAccount())) { return error(ErrorMessages.APPLICATION_ACCESS_DENIED, Response.status(Response.Status.BAD_REQUEST)); } if (connection.getProviderId().equals(ProviderIdConstants.JIRA_PROVIDER_ID)) { projectHostingClient = new JiraClient(connection); ProjectHostingIssue issue = new ProjectHostingIssue(); issue.setType(getJSON(json, "type")); issue.setProject("project"); issue.setSummary("summary"); issue.setDescription("description"); try { return Response.ok(((JiraClient) projectHostingClient).createIssue(issue)).build(); } catch (SOAPException e) { return error("Error creating Jira issue using SOAP API for connection [" + connection.getId() + "]: " + e.getMessage(), Response.status(Response.Status.INTERNAL_SERVER_ERROR)); } } else { return error("The connection type for the id specified does not support creating external issues.", Response.status(Response.Status.BAD_REQUEST)); } } catch (ConnectionNotFoundException e) { return error(e.getMessage(), Response.status(Response.Status.NOT_FOUND)); } finally { if (projectHostingClient != null) { projectHostingClient.cleanUp(); } } } /** * @response.representation.400.doc If a client attempts to update a connection not accessible to the client, or if new credentials for the connection do not work. * @response.representation.404.doc Returned when a connection id is not found * @response.representation.409.doc If a connection being updated will have no attributes that make it a duplicate of another connection * the connection fails validation * @resource.representation.500.doc Returned when the resource creation request to external provider failed */ @PUT @Path("{id}") @ApiOperation(value = "Update an existing connection.", notes = "The response is the new representation of the connection.", responseClass = "com.streamreduce.rest.dto.response.ConnectionResponseDTO" ) public Response updateConnection(@ApiParam(name = "id", required = true) @PathParam("id") String id, @ApiParam(name = "connection", required = true, value = "A JSON object with " + "all connection fields to be updated") JSONObject json) { if (StringUtils.isBlank(id)) { return error(ErrorMessages.MISSING_REQUIRED_FIELD, Response.status(Response.Status.BAD_REQUEST)); } ObjectId objectId = new ObjectId(id); try { Connection connection = connectionService.getConnection(objectId); if (!isOwnerOrAdmin(connection.getUser(), connection.getAccount())) { return error(ErrorMessages.APPLICATION_ACCESS_DENIED, Response.status(Response.Status.BAD_REQUEST)); } connection.mergeWithJSON(json); mergeOutboundConfigurations(connection, json); return Response.ok(toFullDTO(connectionService.updateConnection(connection))).build(); } catch (ConnectionNotFoundException e) { return error(e.getMessage(), Response.status(Response.Status.NOT_FOUND)); } catch (ConnectionExistsException e) { return error(e.getMessage(), Response.status(Response.Status.CONFLICT)); } catch (InvalidCredentialsException e) { return error(e.getMessage(), Response.status(Response.Status.BAD_REQUEST)); } catch (IOException e) { return error(e.getMessage(), Response.status(Response.Status.INTERNAL_SERVER_ERROR)); } catch (IllegalArgumentException e) { // This happens with null URLs in project hosting validation (pre-DAO persist) ConstraintViolationExceptionResponseDTO dto = new ConstraintViolationExceptionResponseDTO(); dto.setViolations(ImmutableMap.of("url", e.getMessage())); return Response.status(Response.Status.BAD_REQUEST).entity(dto).build(); } catch (RuntimeException e) { //Catch all for runtime exceptions return error(e.getMessage(), Response.status(Response.Status.BAD_REQUEST)); } } /** * @return the response indicating the result of the deletion * @response.representation.200.doc Returned when a connection is successfully deleted * @response.representation.400.doc Returned when the specified connection does not exist or is inaccessible to the client. * @response.representation.404.doc Returned when the id is not found */ @DELETE @Path("{id}") @ApiOperation(value = "Deletes a connection.") public Response deleteConnection(@PathParam("id") @ApiParam(name = "id", required = true) String id) { if (StringUtils.isBlank(id)) { return error(ErrorMessages.MISSING_REQUIRED_FIELD, Response.status(Response.Status.BAD_REQUEST)); } ObjectId objectId = new ObjectId(id); try { Connection connection = connectionService.getConnection(objectId); Account account = securityService.getCurrentUser().getAccount(); // SOBA-1885 resource is PUBLIC and you are the admin. // AND you are not in the Nodeable account (because we really can delete those) if ((connection.getVisibility().equals(SobaObject.Visibility.PUBLIC) // && securityService.hasRole(Roles.ADMIN_ROLE) // SOBA-1937, allow any user to remove public ) && !applicationManager.getUserService().getSuperUser().getAccount().getId().equals(account.getId())) { // add to blacklist account.appendToPublicConnectionBlacklist(connection.getId()); applicationManager.getUserService().updateAccount(account); // remove messages from inbox if we want // if(connection.getHashtags().contains("#sample")) { // applicationManager.getMessageService().removeSampleMessages(account, connection.getId()); // } return Response.ok().build(); } // ACCOUNT or PRIVATE scope and you are not the owner, or admin if (!isOwnerOrAdmin(connection.getUser(), connection.getAccount())) { return error(ErrorMessages.APPLICATION_ACCESS_DENIED, Response.status(Response.Status.BAD_REQUEST)); } // delete the connection, associated inventory will be removed in next refresh job connectionService.deleteConnection(connection); return Response.ok().build(); } catch (ConnectionNotFoundException e) { return error(e.getMessage(), Response.status(Response.Status.NOT_FOUND)); } } /** * @response.representation.400.doc Returned when the client does not specify a connection id or specifies a connection id the client does not have access to * @response.representation.404.doc Returned when the connection id cannot be found */ @GET @Path("{id}/inventory") @ApiOperation(value = "Returns the inventory items for a given connection", notes = "Inventory items represent assets created and managed by the external provider.", responseClass = "com.streamreduce.rest.dto.response.ConnectionInventoryResponseDTO") public Response getInventory(@ApiParam(name = "id", required = true) @PathParam("id") String id, @ApiParam(name = "id", required = false, defaultValue = "false") @DefaultValue("false") @QueryParam("count") boolean count) { if (id == null) { return error(ErrorMessages.MISSING_REQUIRED_FIELD, Response.status(Response.Status.BAD_REQUEST)); } ObjectId objectId = new ObjectId(id); User currentUser = securityService.getCurrentUser(); Connection connection; try { connection = connectionService.getConnection(objectId); // if it's public it's ok if (!isInAccount(connection.getAccount()) && !connection.getVisibility().equals(SobaObject.Visibility.PUBLIC)) { return error(ErrorMessages.APPLICATION_ACCESS_DENIED, Response.status(Response.Status.BAD_REQUEST)); } } catch (ConnectionNotFoundException e) { return error(e.getMessage(), Response.status(Response.Status.NOT_FOUND)); } InventoryService inventoryService = applicationManager.getInventoryService(); List<InventoryItem> inventoryItems = inventoryService.getInventoryItems(connection, currentUser); if (inventoryItems == null || inventoryItems.size() == 0) { return Response.noContent().build(); } else if (count) { return Response.ok(inventoryItems.size()).build(); } else { ConnectionInventoryResponseDTO dto = new ConnectionInventoryResponseDTO(); List<InventoryItemResponseDTO> inventoryItemDTOs = new ArrayList<>(); for (InventoryItem inventoryItem : inventoryItems) { inventoryItemDTOs.add(toFullDTO(inventoryItem)); } dto.setInventoryItems(inventoryItemDTOs); return Response.ok(dto).build(); } } /** * @response.representation.200.doc Returned when a tag is successfully applied to a connection and all of its inventory items * @response.representation.400.doc Returned if no hashtag is specified, or if the client does not have access to the given connection * @response.representation.404.doc If the connection with the supplied id does not exist */ @POST @Path("{id}/hashtag") @Consumes(MediaType.APPLICATION_JSON) @ApiOperation(value = "Adds the hashtag to a connection and also to all of its inventory items.") @Override public Response addTag(@ApiParam(name = "id", required = true) @PathParam("id") String id, @ApiParam(name = "hashtag", required = true, value = "A JSON object with a hashtag field " + " property that contains the hashtag to be added") JSONObject json) { String hashtag = getJSON(json, HASHTAG); if (isEmpty(hashtag)) { return error("Hashtag payload is empty", Response.status(Response.Status.BAD_REQUEST)); } try { Connection connection = connectionService.getConnection(new ObjectId(id)); User user = securityService.getCurrentUser(); if (!isInAccount(connection.getAccount())) { return error(ErrorMessages.APPLICATION_ACCESS_DENIED, Response.status(Response.Status.BAD_REQUEST)); } connectionService.addHashtag(connection, user, hashtag); // do this in the resources because only users are doing this right now. // probably need to move to the service layer at some point List items = applicationManager.getInventoryService().getInventoryItems(connection, user); if (items != null) { for (Object abstractInventoryItem : items) { applicationManager.getInventoryService().addHashtag((InventoryItem) abstractInventoryItem, user, hashtag); } } } catch (ConnectionNotFoundException e) { return error("No connection with the provided id (" + id.toString() + ") could be found.", Response.status(Response.Status.NOT_FOUND)); } return Response .ok() .build(); } /** * @response.representation.200.doc Returned when a list of hashtags for a given connection is successfully rendered * @response.representation.400.doc Returned if a connection id isn't specified or the client does not have access to the connection. * @response.representation.404.doc If the connection with the supplied id does not exist */ @GET @Path("{id}/hashtag") @ApiOperation(value = "Retrieves a list of all hashtags for a given connection.") @Override public Response getTags(@PathParam("id") @ApiParam(name = "id", required = true) String id) { if (StringUtils.isBlank(id)) { return error(ErrorMessages.MISSING_REQUIRED_FIELD, Response.status(Response.Status.BAD_REQUEST)); } ObjectId objectId = new ObjectId(id); Set<String> tags; Connection connection; try { connection = connectionService.getConnection(objectId); if (!isInAccount(connection.getAccount())) { return error(ErrorMessages.APPLICATION_ACCESS_DENIED, Response.status(Response.Status.BAD_REQUEST)); } } catch (ConnectionNotFoundException e) { return error("No connection with the provided id (" + objectId.toString() + ") could be found.", Response.status(Response.Status.NOT_FOUND)); } tags = connection.getHashtags(); return Response .ok(tags) .build(); } /** * @response.representation.200.doc Returned when a hashtag is successfully removed from a connection and all of its inventory * @response.representation.400.doc Returned if a connection id isn't specified or the client does not have access to the connection. * @response.representation.404.doc If the connection with the supplied id does not exist */ @DELETE @Path("{id}/hashtag/{tagname}") @ApiOperation(value = "Deletes a hashtag from a connection and all of its inventory items.") @Override public Response removeTag(@ApiParam(name = "id", required = true) @PathParam("id") String id, @ApiParam(name = "tagname", required = true) @PathParam("tagname") String hashtag) { if (id == null) { return error(ErrorMessages.MISSING_REQUIRED_FIELD, Response.status(Response.Status.BAD_REQUEST)); } if (isEmpty(hashtag)) { return error("Hashtag payload is empty", Response.status(Response.Status.BAD_REQUEST)); } try { ObjectId objectId = new ObjectId(id); Connection connection = connectionService.getConnection(objectId); if (!isInAccount(connection.getAccount())) { return error(ErrorMessages.APPLICATION_ACCESS_DENIED, Response.status(Response.Status.BAD_REQUEST)); } User user = securityService.getCurrentUser(); connectionService.removeHashtag(connection, user, hashtag); // do this in the resources because only users are doing this right now. // probably need to move to the service layer at some point List items = applicationManager.getInventoryService().getInventoryItems(connection, user); if (items != null) { for (Object abstractInventoryItem : items) { applicationManager.getInventoryService().removeHashtag((InventoryItem) abstractInventoryItem, user, hashtag); } } } catch (ConnectionNotFoundException e) { return error("No connection with the provided id (" + id + ") could be found.", Response.status(Response.Status.NOT_FOUND)); } return Response.ok().build(); } /** * @response.representation.200.doc Returned if the request to refresh inventory immediately was granted. * @response.representation.400.doc Returned if a connection id isn't specified or the client does not have access to the connection. * @response.representation.404.doc If the connection with the supplied id does not exist * @response.representation.500.doc If the refresh of the connection's inventory could not be scheduled */ @POST @Path("{id}/inventory/refresh") @ApiOperation(value = "Immediately refreshes the inventory for the given connection") public Response refreshInventory(@PathParam("id") @ApiParam(name = "id", required = true) String id) { if (id == null) { return error(ErrorMessages.MISSING_REQUIRED_FIELD, Response.status(Response.Status.BAD_REQUEST)); } ObjectId objectId = new ObjectId(id); try { Connection connection; try { connection = connectionService.getConnection(objectId); } catch (ConnectionNotFoundException e) { return error("No connection with the provided id (" + objectId.toString() + ") could be found.", Response.status(Response.Status.NOT_FOUND)); } // only the owner can do this... if (!isOwner(connection.getUser())) { return error(ErrorMessages.APPLICATION_ACCESS_DENIED, Response.status(Response.Status.BAD_REQUEST)); } if (!connection.getType().equals(ConnectionTypeConstants.FEED_TYPE) || !connection.getType().equals(ConnectionTypeConstants.GATEWAY_TYPE)) { connectionService.fireOneTimeHighPriorityJobForConnection(connection); } } catch (ConnectionNotFoundException e) { return error(e.getMessage(), Response.status(Response.Status.NOT_FOUND)); } catch (InvalidCredentialsException e) { return error(e.getMessage(), Response.status(Response.Status.BAD_REQUEST)); } catch (IOException e) { return error(e.getMessage(), Response.status(Response.Status.INTERNAL_SERVER_ERROR)); } return Response.ok().build(); } /** * Expects a jsonObject that contains a child element of outboundConfigurations and converts it to an * OutboundConfiguration[] to be used as varargs to * {@link com.streamreduce.core.model.Connection.Builder#outboundConfigurations(com.streamreduce.core.model.OutboundConfiguration...)} * * @param jsonObject a JSONObject representing an OutboundConfiguration received from the REST API. * @return OutboundConfiguration[] containing all transformed jsonobjects into OutboundConfigurations */ private OutboundConfiguration[] extractOutboundConfigurationsFromJSON(JSONObject jsonObject) throws ConnectionNotFoundException { //There's really no better place to put this unless the model objects start including services so they can //lazily load fields. if (jsonObject == null || !jsonObject.containsKey("outboundConfigurations")) { return new OutboundConfiguration[0]; } List<OutboundConfiguration> outboundConfigurationList = new ArrayList<>(); for (Object o : jsonObject.getJSONArray("outboundConfigurations")) { OutboundConfiguration outboundConfiguration = extractOutboundConfigurationFromJSON((JSONObject) o); outboundConfigurationList.add(outboundConfiguration); } return outboundConfigurationList.toArray(new OutboundConfiguration[outboundConfigurationList.size()]); } private OutboundConfiguration extractOutboundConfigurationFromJSON(JSONObject outboundConfigurationAsJSONObject) { OutboundConfiguration.Builder outboundConfigurationBuilder = new OutboundConfiguration.Builder() .protocol(outboundConfigurationAsJSONObject.getString("protocol")); List<OutboundDataType> outboundDataTypes = new ArrayList<>(); for (Object dataTypeObj : outboundConfigurationAsJSONObject.getJSONArray("dataTypes")) { String dataType = ((String) dataTypeObj).toUpperCase(); outboundDataTypes.add(OutboundDataType.valueOf(dataType)); } outboundConfigurationBuilder.dataTypes(outboundDataTypes.toArray(new OutboundDataType[outboundDataTypes.size()])); if (outboundConfigurationAsJSONObject.containsKey("credentials")) { JSONObject credentialsJSONObject = outboundConfigurationAsJSONObject.getJSONObject("credentials"); String username = credentialsJSONObject.containsKey("username") ? credentialsJSONObject.getString("username") : null; String password = credentialsJSONObject.containsKey("password") ? credentialsJSONObject.getString("password") : null; ConnectionCredentials credentials = new ConnectionCredentials(username, password); outboundConfigurationBuilder.credentials(credentials); } //Non-required fields if (outboundConfigurationAsJSONObject.containsKey("destination")) { outboundConfigurationBuilder.destination(outboundConfigurationAsJSONObject.getString("destination")); } if (outboundConfigurationAsJSONObject.containsKey("namespace")) { outboundConfigurationBuilder.namespace(outboundConfigurationAsJSONObject.getString("namespace")); } return outboundConfigurationBuilder.build(); } @SuppressWarnings("unchecked") private void mergeOutboundConfigurations(Connection connection, JSONObject json) throws ConnectionNotFoundException { if (!json.containsKey("outboundConfigurations")) { return; } // build a map of outbound configs keyed on the protocol + destination Map<String, OutboundConfiguration> currentConfigs = new HashMap<>(); if (!CollectionUtils.isEmpty(connection.getOutboundConfigurations())) { for (OutboundConfiguration outboundConfiguration : connection.getOutboundConfigurations()) { String key = outboundConfiguration.getProtocol(); currentConfigs.put(key, outboundConfiguration); } } JSONArray outboundConfigurations = json.getJSONArray("outboundConfigurations"); for (Iterator<JSONObject> iter = outboundConfigurations.iterator(); iter.hasNext(); ) { JSONObject outboundConfiguration = iter.next(); if (outboundConfiguration.containsKey("protocol")) { // do we know about this config already? String key = outboundConfiguration.getString("protocol"); OutboundConfiguration configuration = null; if (!currentConfigs.containsKey(key)) { configuration = extractOutboundConfigurationFromJSON(outboundConfiguration); connection.addOutboundConfiguration(configuration); } else { configuration = currentConfigs.get(key); configuration.mergeWithJSON(outboundConfiguration); } } } } }