package org.fluxtream.core.api; import com.google.gson.Gson; import com.wordnik.swagger.annotations.*; import net.sf.json.JSONObject; import org.fluxtream.core.auth.AuthHelper; import org.fluxtream.core.auth.TrustRelationshipRevokedException; import org.fluxtream.core.connectors.Connector; import org.fluxtream.core.connectors.updaters.ScheduleResult; import org.fluxtream.core.domain.*; import org.fluxtream.core.services.BuddiesService; import org.fluxtream.core.services.ConnectorUpdateService; import org.fluxtream.core.services.GuestService; import org.fluxtream.core.services.SystemService; import org.fluxtream.core.updaters.quartz.Producer; import org.joda.time.DateTimeZone; import org.joda.time.format.DateTimeFormatter; import org.joda.time.format.ISODateTimeFormat; 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.Collection; import java.util.List; /** * * @author Candide Kemmler (candide@fluxtream.com) */ @Path("/v1/sync") @Component("RESTSyncController") @Api(value = "/sync", description = "Retrieve information about connector state and schedule connector synchronization") @Scope("request") public class SyncController { @Autowired GuestService guestService; @Autowired SystemService sysService; @Autowired ConnectorUpdateService connectorUpdateService; @Autowired BuddiesService buddiesService; Gson gson = new Gson(); private static final DateTimeFormatter fmt = ISODateTimeFormat.dateTime().withZone(DateTimeZone.forID("UTC")); @POST @Path("/{connector}") @ApiOperation(value = "Update a connector belonging to either the logged in user or the buddy-to-access specified in the " + BuddiesService.BUDDY_TO_ACCESS_PARAM + " parameter", response = ScheduleResult.class, responseContainer = "Array") @ApiResponses({ @ApiResponse(code=401, message="The user is no longer logged in"), @ApiResponse(code=403, message="Buddy-to-access authorization has been revoked") }) @Produces({MediaType.APPLICATION_JSON}) public Response updateConnector(@ApiParam(value="Connector name", required=true) @PathParam("connector") String connectorName, @ApiParam(value="Buddy to access username parameter (" + BuddiesService.BUDDY_TO_ACCESS_PARAM + ")", required=false) @QueryParam(BuddiesService.BUDDY_TO_ACCESS_PARAM) String buddyToAccessParameter){ return sync(connectorName, buddyToAccessParameter, true); } private Response sync(final String connectorName, final String buddyToAccessParameter, final boolean force) { try{ TrustedBuddy trustedBuddy; try { trustedBuddy = AuthHelper.getBuddyTrustedBuddy(buddyToAccessParameter, buddiesService); } catch (TrustRelationshipRevokedException e) {return Response.status(403).entity("Sorry, permission to access this data has been revoked. Please reload your browser window").build();} Guest guest = ApiHelper.getBuddyToAccess(guestService, trustedBuddy); final long guestId = guest.getId(); final ApiKey apiKey = guestService.getApiKey(guestId, Connector.getConnector(connectorName)); guestService.setApiKeyToSynching(apiKey.getId(), true); if (apiKey==null) { return Response.status(Response.Status.BAD_REQUEST).entity("we don't have an ApiKey for this connector").build(); } final List<ScheduleResult> scheduleResults = connectorUpdateService.updateConnector(apiKey, force); return Response.ok(scheduleResults).build(); } catch (Exception e){ return Response.serverError().entity("Failed to schedule update: " + e.getMessage()).build(); } } @POST @Path("/{connector}/{objectTypes}") @ApiOperation(value = "Update a connector's object types belonging to eigher the logged in user or the buddy-to-access specified in the " + BuddiesService.BUDDY_TO_ACCESS_PARAM + " parameter", response = ScheduleResult.class, responseContainer = "Array") @ApiResponses({ @ApiResponse(code=401, message="The user is no longer logged in"), @ApiResponse(code=403, message="Buddy-to-access authorization has been revoked") }) @Produces({MediaType.APPLICATION_JSON}) public Response updateConnectorObjectType(@ApiParam(value="Connector name", required=true) @PathParam("connector") String connectorName, @ApiParam(value="Bit mask of object types that have to be updated", required=true) @PathParam("objectTypes") int objectTypes, @ApiParam(value="Buddy to access username parameter (" + BuddiesService.BUDDY_TO_ACCESS_PARAM + ")", required=false) @QueryParam(BuddiesService.BUDDY_TO_ACCESS_PARAM) String buddyToAccessParameter){ return syncConnectorObjectType(connectorName, buddyToAccessParameter, objectTypes, false); } private Response syncConnectorObjectType(final String connectorName, final String buddyToAccessParameter, final int objectTypes, final boolean force) { try { TrustedBuddy trustedBuddy; try { trustedBuddy = AuthHelper.getBuddyTrustedBuddy(buddyToAccessParameter, buddiesService); } catch (TrustRelationshipRevokedException e) {return Response.status(403).entity("Sorry, permission to access this data has been revoked. Please reload your browser window").build();} Guest guest = ApiHelper.getBuddyToAccess(guestService, trustedBuddy); final long guestId = guest.getId(); final ApiKey apiKey = guestService.getApiKey(guestId, Connector.getConnector(connectorName)); final List<ScheduleResult> scheduleResults = connectorUpdateService.updateConnectorObjectType( apiKey, objectTypes, force, false); return Response.ok(scheduleResults).build(); } catch (Exception e) { return Response.serverError().entity("Failed to schedule update: " + e.getMessage()).build(); } } @POST @Path("/all") @ApiOperation(value = "Update all of either the logged in guest's connectors, or the buddy-to-access's connectors", response = ScheduleResult.class, responseContainer = "Array") @ApiResponses({ @ApiResponse(code=401, message="The user is no longer logged in"), @ApiResponse(code=403, message="Buddy-to-access authorization has been revoked") }) @Produces({MediaType.APPLICATION_JSON}) public Response updateAllConnectors(@ApiParam(value="Buddy to access username parameter (" + BuddiesService.BUDDY_TO_ACCESS_PARAM + ")", required=false) @QueryParam(BuddiesService.BUDDY_TO_ACCESS_PARAM) String buddyToAccessParameter){ TrustedBuddy trustedBuddy; try { trustedBuddy = AuthHelper.getBuddyTrustedBuddy(buddyToAccessParameter, buddiesService); } catch (TrustRelationshipRevokedException e) {return Response.status(403).entity("Sorry, permission to access this data has been revoked. Please reload your browser window").build();} Guest guest = ApiHelper.getBuddyToAccess(guestService, trustedBuddy); final long guestId = guest.getId(); try { final List<ScheduleResult> scheduleResults = connectorUpdateService.updateAllConnectors(guestId, true); return Response.ok(scheduleResults).build(); } catch (Exception e){ return Response.serverError().entity("Failed to schedule udpates: " + e.getMessage()).build(); } } @POST @Path("/producerTest") @Produces({MediaType.APPLICATION_JSON}) public String TestUpdate() throws InterruptedException { Producer p = new Producer(); p.scheduleIncrementalUpdates(); return gson.toJson(null); } @ApiModel public class HistoryCompleteModel { @ApiModelProperty(value="Has the initial history update completed?", required=true) public boolean historyUpdateComplete; } @POST @ApiOperation(value = "Check if a connector's history update is complete", response=HistoryCompleteModel.class) @Path("/{connector}/historyComplete") public Response isHistoryComplete(@ApiParam(value="Connector name", required=true) @PathParam("connector") String connectorName, @ApiParam(value="Bit mask of the connector's object types", required=true) @FormParam("objectTypes") int objectTypes) { final long guestId = AuthHelper.getGuestId(); final ApiKey apiKey = guestService.getApiKey(guestId, Connector.getConnector(connectorName)); final boolean historyUpdateCompleted = connectorUpdateService.isHistoryUpdateCompleted(apiKey, objectTypes); JSONObject response = new JSONObject(); response.accumulate("historyUpdateCompleted", historyUpdateCompleted); return Response.ok(response.toString()).build(); } @ApiModel public class IsSynchingModel { @ApiModelProperty(value="Is this connector currently synching?", required=true) public boolean synching; } @POST @ApiOperation(value = "Check if a connector's currently synching", response = IsSynchingModel.class) @Path("/{connector}/isSynching") public Response isSynching(@ApiParam(value="Connector name", required=true) @PathParam("connector") String connectorName) { final long guestId = AuthHelper.getGuestId(); final ApiKey apiKey = guestService.getApiKey(guestId, Connector.getConnector(connectorName)); final Collection<UpdateWorkerTask> scheduledUpdates = connectorUpdateService.getUpdatingUpdateTasks(apiKey); JSONObject response = new JSONObject(); response.accumulate("synching", scheduledUpdates.size()>0); return Response.ok(response.toString()).build(); } @ApiModel public class LastSuccessfulUpdateModel { @ApiModelProperty(value="ISO 8601 formatted time of last successful update", required=true) public String lastSuccessfulUpdate; } @POST @ApiOperation(value = "Retrieve a connector's last successful update time", response = LastSuccessfulUpdateModel.class) @Path("/{connector}/lastSuccessfulUpdate") public Response lastSuccessfulUpdate(@ApiParam(value="Connector name", required=true) @PathParam("connector") String connectorName) { Connector connector = Connector.getConnector(connectorName); Guest guest = AuthHelper.getGuest(); final ApiKey apiKey = guestService.getApiKey(guest.getId(), connector); final ApiUpdate lastSuccessfulUpdate = connectorUpdateService.getLastSuccessfulUpdate(apiKey); JSONObject response = new JSONObject(); response.accumulate("lastSuccessfulUpdate", lastSuccessfulUpdate!=null ? fmt.print(lastSuccessfulUpdate.ts) : "never"); return Response.ok(response.toString()).build(); } @POST @Path("/{connector}/reset") @ApiOperation(value = "Un-schedule pending updates of the given connector") @ApiResponses({ @ApiResponse(code=200, message="reset controller {connectorName}") }) @Produces({MediaType.APPLICATION_JSON}) public Response resetConnector(@ApiParam(value="Connector name", required=true) @PathParam("connector") String connectorName) { final long guestId = AuthHelper.getGuestId(); final ApiKey apiKey = guestService.getApiKey(guestId, Connector.getConnector(connectorName)); connectorUpdateService.flushUpdateWorkerTasks(apiKey, true); return Response.ok("reset controller " + connectorName).build(); } @ApiModel public class LastUpdateModel { @ApiModelProperty(value="ISO 8601 formatted time of last update", required=true) public String lastUpdate; } @POST @ApiOperation(value = "Retrieve a connector's last attempted update time (successful or failed)", response = LastUpdateModel.class) @Path("/{connector}/lastUpdate") public Response lastUpdate(@ApiParam(value="Connector name", required=true) @PathParam("connector") String connectorName) { Connector connector = Connector.getConnector(connectorName); Guest guest = AuthHelper.getGuest(); final ApiKey apiKey = guestService.getApiKey(guest.getId(), connector); final ApiUpdate lastUpdate = connectorUpdateService.getLastUpdate(apiKey); JSONObject response = new JSONObject(); response.accumulate("lastUpdate", lastUpdate!=null ? fmt.print(lastUpdate.ts) : "never"); return Response.ok(response.toString()).build(); } }