package edu.harvard.iq.dataverse.api;
import edu.harvard.iq.dataverse.Dataverse;
import edu.harvard.iq.dataverse.DataverseServiceBean;
import edu.harvard.iq.dataverse.harvest.client.HarvestingClient;
import edu.harvard.iq.dataverse.authorization.users.AuthenticatedUser;
import edu.harvard.iq.dataverse.engine.command.DataverseRequest;
import edu.harvard.iq.dataverse.engine.command.impl.CreateHarvestingClientCommand;
import edu.harvard.iq.dataverse.engine.command.impl.GetHarvestingClientCommand;
import edu.harvard.iq.dataverse.engine.command.impl.UpdateHarvestingClientCommand;
import edu.harvard.iq.dataverse.harvest.client.ClientHarvestRun;
import edu.harvard.iq.dataverse.harvest.client.HarvesterServiceBean;
import edu.harvard.iq.dataverse.harvest.client.HarvestingClientServiceBean;
import edu.harvard.iq.dataverse.util.json.JsonParseException;
import javax.json.JsonObjectBuilder;
import static edu.harvard.iq.dataverse.util.json.NullSafeJsonBuilder.jsonObjectBuilder;
import java.io.IOException;
import java.io.StringReader;
import java.util.ArrayList;
import java.util.List;
import java.util.logging.Logger;
import javax.ejb.EJB;
import javax.ejb.Stateless;
import javax.json.Json;
import javax.json.JsonArrayBuilder;
import javax.json.JsonObject;
import javax.ws.rs.GET;
import javax.ws.rs.POST;
import javax.ws.rs.PUT;
import javax.ws.rs.Path;
import javax.ws.rs.PathParam;
import javax.ws.rs.QueryParam;
import javax.ws.rs.core.Response;
@Stateless
@Path("harvest/clients")
public class HarvestingClients extends AbstractApiBean {
@EJB
DataverseServiceBean dataverseService;
@EJB
HarvesterServiceBean harvesterService;
@EJB
HarvestingClientServiceBean harvestingClientService;
private static final Logger logger = Logger.getLogger(HarvestingClients.class.getName());
/*
* /api/harvest/clients
* and
* /api/harvest/clients/{nickname}
* will, by default, return a JSON record with the information about the
* configured remote archives.
* optionally, plain text output may be provided as well.
*/
@GET
@Path("")
public Response harvestingClients(@QueryParam("key") String apiKey) throws IOException {
List<HarvestingClient> harvestingClients = null;
try {
harvestingClients = harvestingClientService.getAllHarvestingClients();
} catch (Exception ex) {
return error( Response.Status.INTERNAL_SERVER_ERROR, "Caught an exception looking up configured harvesting clients; " + ex.getMessage() );
}
if (harvestingClients == null) {
// returning an empty list:
return ok(jsonObjectBuilder().add("harvestingClients",""));
}
JsonArrayBuilder hcArr = Json.createArrayBuilder();
for (HarvestingClient harvestingClient : harvestingClients) {
// We already have this harvestingClient - wny do we need to
// execute this "Get HarvestingClients Client Command" in order to get it,
// again? - the purpose of the command is to run the request through
// the Authorization system, to verify that they actually have
// the permission to view this harvesting client config. -- L.A. 4.4
HarvestingClient retrievedHarvestingClient = null;
try {
DataverseRequest req = createDataverseRequest(findUserOrDie());
retrievedHarvestingClient = execCommand( new GetHarvestingClientCommand(req, harvestingClient));
} catch (Exception ex) {
// Don't do anything.
// We'll just skip this one - since this means the user isn't
// authorized to view this client configuration.
}
if (retrievedHarvestingClient != null) {
hcArr.add(harvestingConfigAsJson(retrievedHarvestingClient));
}
}
return ok(jsonObjectBuilder().add("harvestingClients", hcArr));
}
@GET
@Path("{nickName}")
public Response harvestingClient(@PathParam("nickName") String nickName, @QueryParam("key") String apiKey) throws IOException {
HarvestingClient harvestingClient = null;
try {
harvestingClient = harvestingClientService.findByNickname(nickName);
} catch (Exception ex) {
logger.warning("Exception caught looking up harvesting client " + nickName + ": " + ex.getMessage());
return error( Response.Status.BAD_REQUEST, "Internal error: failed to look up harvesting client " + nickName + ".");
}
if (harvestingClient == null) {
return error(Response.Status.NOT_FOUND, "Harvesting client " + nickName + " not found.");
}
HarvestingClient retrievedHarvestingClient = null;
try {
// findUserOrDie() and execCommand() both throw WrappedResponse
// exception, that already has a proper HTTP response in it.
retrievedHarvestingClient = execCommand(new GetHarvestingClientCommand(createDataverseRequest(findUserOrDie()), harvestingClient));
logger.info("retrieved Harvesting Client " + retrievedHarvestingClient.getName() + " with the GetHarvestingClient command.");
} catch (WrappedResponse wr) {
return wr.getResponse();
} catch (Exception ex) {
logger.warning("Unknown exception caught while executing GetHarvestingClientCommand: "+ex.getMessage());
retrievedHarvestingClient = null;
}
if (retrievedHarvestingClient == null) {
return error( Response.Status.BAD_REQUEST,
"Internal error: failed to retrieve harvesting client " + nickName + ".");
}
try {
return ok(harvestingConfigAsJson(retrievedHarvestingClient));
} catch (Exception ex) {
logger.warning("Unknown exception caught while trying to format harvesting client config as json: "+ex.getMessage());
return error( Response.Status.BAD_REQUEST,
"Internal error: failed to produce output for harvesting client " + nickName + ".");
}
}
@POST
@Path("{nickName}")
public Response createHarvestingClient(String jsonBody, @PathParam("nickName") String nickName, @QueryParam("key") String apiKey) throws IOException, JsonParseException {
try ( StringReader rdr = new StringReader(jsonBody) ) {
JsonObject json = Json.createReader(rdr).readObject();
HarvestingClient harvestingClient = new HarvestingClient();
// TODO: check that it doesn't exist yet...
harvestingClient.setName(nickName);
String dataverseAlias = jsonParser().parseHarvestingClient(json, harvestingClient);
Dataverse ownerDataverse = dataverseService.findByAlias(dataverseAlias);
if (ownerDataverse == null) {
return error(Response.Status.BAD_REQUEST, "No such dataverse: " + dataverseAlias);
}
harvestingClient.setDataverse(ownerDataverse);
if (ownerDataverse.getHarvestingClientConfigs() == null) {
ownerDataverse.setHarvestingClientConfigs(new ArrayList<>());
}
ownerDataverse.getHarvestingClientConfigs().add(harvestingClient);
DataverseRequest req = createDataverseRequest(findUserOrDie());
HarvestingClient managedHarvestingClient = execCommand( new CreateHarvestingClientCommand(req, harvestingClient));
return created( "/harvest/clients/" + nickName, harvestingConfigAsJson(managedHarvestingClient));
} catch (JsonParseException ex) {
return error( Response.Status.BAD_REQUEST, "Error parsing harvesting client: " + ex.getMessage() );
} catch (WrappedResponse ex) {
return ex.getResponse();
}
}
@PUT
@Path("{nickName}")
public Response modifyHarvestingClient(String jsonBody, @PathParam("nickName") String nickName, @QueryParam("key") String apiKey) throws IOException, JsonParseException {
HarvestingClient harvestingClient = null;
try {
harvestingClient = harvestingClientService.findByNickname(nickName);
} catch (Exception ex) {
// We don't care what happened; we'll just assume we couldn't find it.
harvestingClient = null;
}
if (harvestingClient == null) {
return error( Response.Status.NOT_FOUND, "Harvesting client " + nickName + " not found.");
}
String ownerDataverseAlias = harvestingClient.getDataverse().getAlias();
try ( StringReader rdr = new StringReader(jsonBody) ) {
DataverseRequest req = createDataverseRequest(findUserOrDie());
JsonObject json = Json.createReader(rdr).readObject();
String newDataverseAlias = jsonParser().parseHarvestingClient(json, harvestingClient);
if (newDataverseAlias != null
&& !newDataverseAlias.equals("")
&& !newDataverseAlias.equals(ownerDataverseAlias)) {
return error(Response.Status.BAD_REQUEST, "Bad \"dataverseAlias\" supplied. Harvesting client "+nickName+" belongs to the dataverse "+ownerDataverseAlias);
}
HarvestingClient managedHarvestingClient = execCommand( new UpdateHarvestingClientCommand(req, harvestingClient));
return created( "/datasets/" + nickName, harvestingConfigAsJson(managedHarvestingClient));
} catch (JsonParseException ex) {
return error( Response.Status.BAD_REQUEST, "Error parsing harvesting client: " + ex.getMessage() );
} catch (WrappedResponse ex) {
return ex.getResponse();
}
}
// TODO:
// add a @DELETE method
// (there is already a DeleteHarvestingClient command)
// Methods for managing harvesting runs (jobs):
// This POST starts a new harvesting run:
@POST
@Path("{nickName}/run")
public Response startHarvestingJob(@PathParam("nickName") String clientNickname, @QueryParam("key") String apiKey) throws IOException {
try {
AuthenticatedUser authenticatedUser = null;
try {
authenticatedUser = findAuthenticatedUserOrDie();
} catch (WrappedResponse wr) {
return error(Response.Status.UNAUTHORIZED, "Authentication required to use this API method");
}
if (authenticatedUser == null || !authenticatedUser.isSuperuser()) {
return error(Response.Status.FORBIDDEN, "Only the Dataverse Admin user can run harvesting jobs");
}
HarvestingClient harvestingClient = harvestingClientService.findByNickname(clientNickname);
if (harvestingClient == null) {
return error(Response.Status.NOT_FOUND, "No such dataverse: "+clientNickname);
}
DataverseRequest dataverseRequest = createDataverseRequest(authenticatedUser);
harvesterService.doAsyncHarvest(dataverseRequest, harvestingClient);
} catch (Exception e) {
return this.error(Response.Status.BAD_REQUEST, "Exception thrown when running harvesting client\""+clientNickname+"\" via REST API; " + e.getMessage());
}
return this.accepted();
}
// This GET shows the status of the harvesting run in progress for this
// client, if present:
// @GET
// @Path("{nickName}/run")
// TODO:
// This DELETE kills the harvesting run in progress for this client,
// if present:
// @DELETE
// @Path("{nickName}/run")
// TODO:
/* Auxiliary, helper methods: */
/*
@Deprecated
public static JsonArrayBuilder harvestingConfigsAsJsonArray(List<Dataverse> harvestingDataverses) {
JsonArrayBuilder hdArr = Json.createArrayBuilder();
for (Dataverse hd : harvestingDataverses) {
hdArr.add(harvestingConfigAsJson(hd.getHarvestingClientConfig()));
}
return hdArr;
}*/
public static JsonObjectBuilder harvestingConfigAsJson(HarvestingClient harvestingConfig) {
if (harvestingConfig == null) {
return null;
}
return jsonObjectBuilder().add("nickName", harvestingConfig.getName()).
add("dataverseAlias", harvestingConfig.getDataverse().getAlias()).
add("type", harvestingConfig.getHarvestType()).
add("harvestUrl", harvestingConfig.getHarvestingUrl()).
add("archiveUrl", harvestingConfig.getArchiveUrl()).
add("archiveDescription",harvestingConfig.getArchiveDescription()).
add("metadataFormat", harvestingConfig.getMetadataPrefix()).
add("set", harvestingConfig.getHarvestingSet() == null ? "N/A" : harvestingConfig.getHarvestingSet()).
add("schedule", harvestingConfig.isScheduled() ? harvestingConfig.getScheduleDescription() : "none").
add("status", harvestingConfig.isHarvestingNow() ? "inProgress" : "inActive").
add("lastHarvest", harvestingConfig.getLastHarvestTime() == null ? "N/A" : harvestingConfig.getLastHarvestTime().toString()).
add("lastResult", harvestingConfig.getLastResult()).
add("lastSuccessful", harvestingConfig.getLastSuccessfulHarvestTime() == null ? "N/A" : harvestingConfig.getLastSuccessfulHarvestTime().toString()).
add("lastNonEmpty", harvestingConfig.getLastNonEmptyHarvestTime() == null ? "N/A" : harvestingConfig.getLastNonEmptyHarvestTime().toString()).
add("lastDatasetsHarvested", harvestingConfig.getLastHarvestedDatasetCount() == null ? "N/A" : harvestingConfig.getLastHarvestedDatasetCount().toString()).
add("lastDatasetsDeleted", harvestingConfig.getLastDeletedDatasetCount() == null ? "N/A" : harvestingConfig.getLastDeletedDatasetCount().toString()).
add("lastDatasetsFailed", harvestingConfig.getLastFailedDatasetCount() == null ? "N/A" : harvestingConfig.getLastFailedDatasetCount().toString());
}
}