package org.fluxtream.core.api;
import com.sun.jersey.api.Responses;
import com.wordnik.swagger.annotations.*;
import org.fluxtream.core.auth.AuthHelper;
import org.fluxtream.core.connectors.Connector;
import org.fluxtream.core.connectors.updaters.AbstractUpdater;
import org.fluxtream.core.connectors.updaters.SharedConnectorSettingsAwareUpdater;
import org.fluxtream.core.domain.*;
import org.fluxtream.core.mvc.models.CoachModel;
import org.fluxtream.core.mvc.models.GuestModel;
import org.fluxtream.core.mvc.models.SharedChannelModel;
import org.fluxtream.core.mvc.models.SharedConnectorModel;
import org.fluxtream.core.services.BodyTrackStorageService;
import org.fluxtream.core.services.BuddiesService;
import org.fluxtream.core.services.GuestService;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Scope;
import org.springframework.stereotype.Component;
import javax.ws.rs.*;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Response;
import java.util.ArrayList;
import java.util.List;
import java.util.Set;
/**
*
* @author Candide Kemmler (candide@fluxtream.com)
*/
@Path("/v1/buddies")
@Api(value = "/buddies", description = "Data sharing")
@Component("RESTBuddiesController")
@Scope("request")
public class BuddiesController {
@Autowired
GuestService guestService;
@Autowired
BuddiesService buddiesService;
@Autowired
BodyTrackStorageService bodyTrackStorageService;
@Autowired
BeanFactory beanFactory;
@POST
@Path("/find")
@ApiOperation(value = "Find a buddy", response = GuestModel.class)
@ApiResponses({
@ApiResponse(code=400, message="The user is already trusted by the calling guest"),
@ApiResponse(code=404, message="No such user was found")
})
@Produces({MediaType.APPLICATION_JSON})
public Response findCoach(@ApiParam(value="The buddy's username", required=true) @FormParam("username") String username) {
final Guest guest = guestService.getGuest(username);
final List<Guest> coaches = buddiesService.getTrustedBuddies(AuthHelper.getGuestId());
if (coaches.contains(guest))
return Response.status(Response.Status.BAD_REQUEST).entity(username + " is already in you coaching buddies list").build();
if (guest!=null) {
return Response.ok(new GuestModel(guest)).build();
} else
return Responses.notFound().entity("No Such User: " + username + ". Please try again.").build();
}
@DELETE
@Path("/trusted/{username}")
@ApiOperation(value = "Remove a buddy, revoking all access to the calling guest's data",responseContainer = "Array",
response = GuestModel.class)
@Produces({MediaType.APPLICATION_JSON})
public List<GuestModel> removeCoach(@ApiParam(value="The buddy's username", required=true) @PathParam("username") String username) {
final long guestId = AuthHelper.getGuestId();
buddiesService.removeTrustedBuddy(guestId, username);
final List<Guest> coaches = buddiesService.getTrustedBuddies(guestId);
final List<GuestModel> guestModels = toGuestModels(coaches);
return guestModels;
}
@POST
@Path("/trusted/{username}")
@ApiOperation(value = "Add a buddy to whom we are now able to allow access to the calling guest's connectors",
response = GuestModel.class, responseContainer = "Array")
@Produces({MediaType.APPLICATION_JSON})
public List<GuestModel> addCoach(@ApiParam(value="The buddy's username", required=true) @PathParam("username") String username) {
final long guestId = AuthHelper.getGuestId();
buddiesService.addTrustedBuddy(guestId, username);
final List<Guest> coaches = buddiesService.getTrustedBuddies(guestId);
final List<GuestModel> guestModels = toGuestModels(coaches);
return guestModels;
}
List<GuestModel> toGuestModels(List<Guest> guests) {
List<GuestModel> models = new ArrayList<GuestModel>();
for (Guest guest : guests) {
models.add(new GuestModel(guest));
}
return models;
}
@PUT
@Path("/trusted/{username}/channels/{channelId}/shared")
@ApiOperation(value = "Retrieve information about data shared with a given buddy")
public Response setChannelSharingStatus(@ApiParam(value="The buddy's username", required=true) @PathParam("username") String username,
@ApiParam(value="The channel ID", required=true) @PathParam("channelId") long channelId,
@ApiParam(value="Share the channel?", required=true) @FormParam("value") boolean shared) {
final long trustingBuddyId = AuthHelper.getGuestId();
final TrustedBuddy trustedBuddy = buddiesService.getTrustedBuddy(trustingBuddyId, username);
if (trustedBuddy ==null)
return Response.status(Response.Status.NOT_FOUND).entity("Could not buddy with username '" + username + "'").build();
if (!shared) {
List<SharedChannel> sharedChannels = buddiesService.getSharedChannels(trustedBuddy.buddyId, trustingBuddyId);
boolean foundChannel = false;
for (SharedChannel sharedChannel : sharedChannels) {
if (sharedChannel.channelMapping.getId() == channelId) {
foundChannel = true;
break;
}
}
if (!foundChannel)
return Response.status(Response.Status.NOT_FOUND).entity("Could not find channel [" + channelId + "]").build();
buddiesService.removeSharedChannel(trustedBuddy.buddyId, trustingBuddyId, channelId);
} else
buddiesService.addSharedChannel(trustedBuddy.buddyId, trustingBuddyId, channelId);
return Response.ok().build();
}
@GET
@Path("/trusted/{username}/connectors")
@ApiOperation(value = "Retrieve information about data shared with a given buddy",
response = CoachModel.class)
@Produces({MediaType.APPLICATION_JSON})
public CoachModel getConnectorSharingInfo(@ApiParam(value="The buddy's username", required=true) @PathParam("username") String username) {
final long trustingBuddyId = AuthHelper.getGuestId();
final TrustedBuddy trustedBuddy = buddiesService.getTrustedBuddy(trustingBuddyId, username);
final Set<SharedConnector> sharedConnectors = trustedBuddy.sharedConnectors;
final List<ApiKey> apiKeys = guestService.getApiKeys(trustingBuddyId);
CoachModel coach = new CoachModel();
Guest trustedBuddyGuest = guestService.getGuest(username);
coach.username = trustedBuddyGuest.username;
coach.fullname = trustedBuddyGuest.getGuestName();
addSharedConnectors(sharedConnectors, apiKeys, coach);
addSharedChannels(apiKeys, coach, trustingBuddyId, trustedBuddyGuest.getId());
return coach;
}
private void addSharedChannels(List<ApiKey> apiKeys, CoachModel coach, long trustingBuddyId, long trustedBuddyId) {
for (ApiKey apiKey : apiKeys) {
// get all availabale channel mappings
List<ChannelMapping> channelMappings = bodyTrackStorageService.getChannelMappings(apiKey.getId());
// now those that are shared
List<SharedChannel> sharedChannels = buddiesService.getSharedChannels(trustedBuddyId, trustingBuddyId, apiKey.getId());
// create the list of models that we're going to add items to
List<SharedChannelModel> sharedChannelModels = new ArrayList<SharedChannelModel>();
for (ChannelMapping channelMapping : channelMappings) {
// create a SharedChannelModel for each Channel Mapping – shared is false by default
SharedChannelModel sharedChannelModel = new SharedChannelModel();
sharedChannelModel.channelId = channelMapping.getId();
sharedChannelModel.deviceName = channelMapping.getDeviceName().trim();
sharedChannelModel.channelName = channelMapping.getChannelName().trim();
if (channelMapping.getInternalDeviceName()!=null&&!channelMapping.getInternalDeviceName().equals(channelMapping.getDeviceName())) {
sharedChannelModel.userData = true;
sharedChannelModel.deviceName = channelMapping.getInternalDeviceName().trim();
}
// iterate through sharedChannels to check if it is shared
for (SharedChannel sharedChannel : sharedChannels) {
if (sharedChannel.channelMapping.getId().equals(channelMapping.getId())) {
sharedChannelModel.shared = true;
break;
}
}
if (sharedChannelModel.userData) {
List<SharedChannelModel> userDataChannels = coach.sharedChannels.get(sharedChannelModel.deviceName);
if (userDataChannels == null) {
userDataChannels = new ArrayList<SharedChannelModel>();
coach.sharedChannels.put(sharedChannelModel.deviceName, userDataChannels);
}
userDataChannels.add(sharedChannelModel);
} else
sharedChannelModels.add(sharedChannelModel);
}
// finally add an entry to our coach model's sharedChannels map
coach.sharedChannels.put(apiKey.getConnector().getDeviceNickname(), sharedChannelModels);
}
}
private void addSharedConnectors(Set<SharedConnector> sharedConnectors, List<ApiKey> apiKeys, CoachModel coach) {
List<SharedConnectorModel> connectors = new ArrayList<SharedConnectorModel>();
for (ApiKey apiKey : apiKeys) {
boolean isShared = false;
// Make sure this apiKey is valid, skip if not
if(apiKey==null || apiKey.getConnector()==null || apiKey.getConnector().getName()==null)
continue;
final String connectorName = apiKey.getConnector().getName();
for (SharedConnector sharedConnector : sharedConnectors) {
if (sharedConnector.connectorName.equals(connectorName)) {
isShared = true;
break;
}
}
SharedConnectorModel connector = new SharedConnectorModel();
connector.prettyName = apiKey.getConnector().prettyName();
connector.connectorName = connectorName;
connector.shared = isShared;
connector.apiKeyId = apiKey.getId();
if (SharedConnectorSettingsAwareUpdater.class.isAssignableFrom(apiKey.getConnector().getUpdaterClass()))
connector.hasSettings = true;
connectors.add(connector);
}
coach.sharedConnectors = connectors;
}
@GET
@Path("/trusted")
@ApiOperation(value = "Retrieve the list of buddies whose data we may have access to",
response = GuestModel.class, responseContainer = "Array")
@Produces({MediaType.APPLICATION_JSON})
public List<GuestModel> getTrustedBuddies(){
Guest guest = AuthHelper.getGuest();
final List<Guest> trustedBuddies = buddiesService.getTrustedBuddies(guest.getId());
final List<GuestModel> guestModels = toGuestModels(trustedBuddies);
return guestModels;
}
@GET
@Path("/trusting")
@ApiOperation(value = "Retrieve the list of buddies with whom the calling guest may have shared data",
response = GuestModel.class, responseContainer = "Array")
@Produces({MediaType.APPLICATION_JSON})
public List<GuestModel> getTrustingBuddies(){
final long guestId = AuthHelper.getGuestId();
final List<Guest> trustingBuddies = buddiesService.getTrustingBuddies(guestId);
final List<GuestModel> guestModels = toGuestModels(trustingBuddies);
return guestModels;
}
@POST
@Path("/trusted/{username}/connectors/{connector}")
@ApiOperation(value = "Share a connector with a buddy")
@ApiResponses(value = { @ApiResponse(code = 200, message = "Successfully added a connector ({username}/{connectorName})")})
public Response addSharedConnector(@ApiParam(value="The buddy's username", required=true) @PathParam("username") String username,
@ApiParam(value="A connector name", required=true) @PathParam("connector") String connectorName) {
final SharedConnector sharedConnector = buddiesService.addSharedConnector(AuthHelper.getGuestId(), username, connectorName, "{}");
final ApiKey apiKey = guestService.getApiKey(AuthHelper.getGuestId(), Connector.getConnector(connectorName));
final Class<? extends AbstractUpdater> updaterClass = apiKey.getConnector().getUpdaterClass();
if (SharedConnectorSettingsAwareUpdater.class.isAssignableFrom(updaterClass)) {
final SharedConnectorSettingsAwareUpdater updater = (SharedConnectorSettingsAwareUpdater) beanFactory.getBean(updaterClass);
updater.syncSharedConnectorSettings(apiKey.getId(), sharedConnector);
}
return Response.ok("Successfully added a connector (" + username + "/" + connectorName + ")").build();
}
@DELETE
@Path("/trusted/{username}/connectors/{connector}")
@ApiOperation(value = "Stop sharing a connector with a buddy")
@ApiResponses(value = { @ApiResponse(code = 200, message = "Successfully removed a connector ({username}/{connectorName})")})
public Response removeSharedConnector(@ApiParam(value="The buddy's username", required=true) @PathParam("username") String username,
@ApiParam(value="A connector name", required=true) @PathParam("connector") String connectorName) {
buddiesService.removeSharedConnector(AuthHelper.getGuestId(), username, connectorName);
return Response.ok().entity("Successfully removed a connector (" + username + "/" + connectorName + ")").build();
}
@GET
@Path("/trusted/sharedConnector/{apiKeyId}/{username}")
@Produces({MediaType.APPLICATION_JSON})
@ApiOperation(value = "Retrieve sharing details for a given connector", notes= "Note: the structure of the returned object is connector specific")
public String getSharedConnectorSettings(@ApiParam(value="ID of a connector instance", required=true) @PathParam("apiKeyId") long apiKeyId,
@ApiParam(value="The buddy's username", required=true) @PathParam("username") String username) {
final long buddyId = guestService.getGuest(username).getId();
final SharedConnector sharedConnector = buddiesService.getSharedConnector(apiKeyId, buddyId);
return sharedConnector.filterJson;
}
@POST
@Path("/trusted/sharedConnector/{apiKeyId}/{username}")
@ApiOperation(value = "Specify sharing details for a given connector – the structure of the specification object is connector specific")
@ApiResponses(value = { @ApiResponse(code = 200, message = "Saved shared connector filter object")})
public Response saveSharedConnectorSettingsFilter(@ApiParam(value="ID of a connector instance", required=true) @PathParam("apiKeyId") long apiKeyId,
@ApiParam(value="The buddy's username", required=true) @PathParam("username") String username,
@ApiParam(value="Custom connector sharing specification", required=true) @FormParam("json") String json) {
final ApiKey apiKey = guestService.getApiKey(apiKeyId);
final long guestId = AuthHelper.getGuestId();
final long buddyId = guestService.getGuest(username).getId();
try {
if (apiKey.getGuestId()!=guestId)
throw new RuntimeException("attempt to retrieve ApiKey from another guest!");
final SharedConnector sharedConnector = buddiesService.getSharedConnector(apiKeyId, buddyId);
buddiesService.setSharedConnectorFilter(sharedConnector.getId(), json);
} catch (Throwable e) {
return Response.serverError().entity(e.getMessage()).build();
}
return Response.ok("Saved shared connector filter object").build();
}
}