package eu.europeana.cloud.mcs.driver;
import eu.europeana.cloud.common.model.Permission;
import eu.europeana.cloud.common.model.Record;
import eu.europeana.cloud.common.model.Representation;
import eu.europeana.cloud.common.response.RepresentationRevisionResponse;
import eu.europeana.cloud.common.response.ErrorInfo;
import eu.europeana.cloud.common.web.ParamConstants;
import eu.europeana.cloud.mcs.driver.filter.ECloudBasicAuthFilter;
import eu.europeana.cloud.service.mcs.exception.*;
import eu.europeana.cloud.service.mcs.status.McsErrorCode;
import org.glassfish.jersey.client.authentication.HttpAuthenticationFeature;
import org.glassfish.jersey.media.multipart.FormDataMultiPart;
import org.glassfish.jersey.media.multipart.MultiPartFeature;
import org.glassfish.jersey.message.internal.MessageBodyProviderNotFoundException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.ws.rs.client.Client;
import javax.ws.rs.client.ClientBuilder;
import javax.ws.rs.client.Entity;
import javax.ws.rs.client.Invocation.Builder;
import javax.ws.rs.client.WebTarget;
import javax.ws.rs.core.Form;
import javax.ws.rs.core.GenericType;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Response;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.InputStream;
import java.net.URI;
import java.util.List;
import static eu.europeana.cloud.common.web.ParamConstants.*;
/**
* Exposes API related to records.
*/
public class RecordServiceClient extends MCSClient {
private final Client client = ClientBuilder.newClient().register(MultiPartFeature.class);
private static final Logger logger = LoggerFactory.getLogger(DataSetServiceClient.class);
//records/{CLOUDID}
private static final String recordPath;
//records/{CLOUDID}/representations
private static final String representationsPath;
//records/{CLOUDID}/representations/{REPRESENTATIONNAME}
private static final String represtationNamePath;
//records/{CLOUDID}/representations/{REPRESENTATIONNAME}/versions
private static final String versionsPath;
//records/{CLOUDID}/representations/{REPRESENTATIONNAME}/versions/{VERSION}
private static final String versionPath;
//records/{CLOUDID}/representations/{REPRESENTATIONNAME}/versions/{VERSION}/copy
private static final String copyPath;
//records/{CLOUDID}/representations/{REPRESENTATIONNAME}/versions/{VERSION}/persist
private static final String persistPath;
//records/{CLOUDID}/representations/{REPRESENTATIONNAME}/versions/{VERSION}/permissions/{TYPE}/users/{USER_NAME}
private static final String grantingPermissionsToVesionPath;
//records/{CLOUDID}/representations/{REPRESENTATIONNAME}/versions/{VERSION}/permit
private static final String permitPath;
//records/{CLOUDID}/representations/{REPRESENTATIONNAME}/revisions/{REVISIONID}
private static final String representationsRevisionsPath;
static {
StringBuilder builder = new StringBuilder();
builder.append(ParamConstants.RECORDS);
builder.append("/");
builder.append("{");
builder.append(P_CLOUDID);
builder.append("}");
recordPath = builder.toString();
builder.append("/");
builder.append(ParamConstants.REPRESENTATIONS);
representationsPath = builder.toString();
builder.append("/");
builder.append("{");
builder.append(P_REPRESENTATIONNAME);
builder.append("}");
represtationNamePath = builder.toString();
builder.append("/");
builder.append(ParamConstants.VERSIONS);
versionsPath = builder.toString();
builder.append("/");
builder.append("{");
builder.append(ParamConstants.P_VER);
builder.append("}");
versionPath = builder.toString();
copyPath = versionPath + "/" + ParamConstants.COPY;
persistPath = versionPath + "/" + ParamConstants.PERSIST;
permitPath = versionPath + "/" + ParamConstants.PERMIT;
grantingPermissionsToVesionPath = versionPath + "/permissions/{" + ParamConstants.P_PERMISSION_TYPE + "}/users/{" + ParamConstants.P_USERNAME + "}";
representationsRevisionsPath = represtationNamePath + "/revisions/{" + P_REVISION_NAME + "}";
}
/**
* Creates instance of RecordServiceClient.
*
* @param baseUrl URL of the MCS Rest Service
*/
public RecordServiceClient(String baseUrl) {
super(baseUrl);
}
/**
* Creates instance of RecordServiceClient. Same as {@link #RecordServiceClient(String)}
* but includes username and password to perform authenticated requests.
*
* @param baseUrl URL of the MCS Rest Service
*/
public RecordServiceClient(String baseUrl, final String username, final String password) {
this(baseUrl);
client.register(HttpAuthenticationFeature.basicBuilder().credentials(username, password).build());
}
/**
* Client will use provided authorization header for all requests;
*
* @param headerValue authorization header value
*/
public void useAuthorizationHeader(final String headerValue) {
client.register(new ECloudBasicAuthFilter(headerValue));
}
/**
* Returns record with all its latest persistent representations.
*
* @param cloudId id of the record (required)
* @return record of specified cloudId (required)
* @throws RecordNotExistsException when id is not known UIS Service
* @throws MCSException on unexpected situations
*/
public Record getRecord(String cloudId)
throws RecordNotExistsException, MCSException {
WebTarget target = client.target(baseUrl).path(recordPath).resolveTemplate(P_CLOUDID, cloudId);
Builder request = target.request();
Response response = null;
try {
response = request.get();
if (response.getStatus() == Response.Status.OK.getStatusCode()) {
return response.readEntity(Record.class);
}
ErrorInfo errorInfo = response.readEntity(ErrorInfo.class);
throw MCSExceptionProvider.generateException(errorInfo);
} finally {
closeResponse(response);
}
}
/**
* Deletes record with all its representations in all versions.
* <p/>
* Does not remove mapping from Unique Identifier Service. If record exists,
* but nothing was deleted (it had no representations assigned), nothing
* happens.
*
* @param cloudId id of deleted record (required)
* @throws RecordNotExistsException if cloudId is not known UIS Service
* @throws MCSException on unexpected situations
*/
public void deleteRecord(String cloudId)
throws RecordNotExistsException, MCSException {
WebTarget target = client.target(baseUrl).path(recordPath).resolveTemplate(P_CLOUDID, cloudId);
Builder request = target.request();
Response response = null;
try {
response = request.delete();
if (response.getStatus() != Response.Status.NO_CONTENT.getStatusCode()) {
ErrorInfo errorInfo = response.readEntity(ErrorInfo.class);
throw MCSExceptionProvider.generateException(errorInfo);
}
} finally {
closeResponse(response);
}
}
/**
* Lists all latest persistent versions of record representation.
*
* @param cloudId id of record from which to get representations (required)
* @return list of representations
* @throws RecordNotExistsException if cloudId is not known UIS Service
* @throws MCSException on unexpected situations
*/
public List<Representation> getRepresentations(String cloudId)
throws RecordNotExistsException, MCSException {
WebTarget target = client.target(baseUrl).path(representationsPath)
.resolveTemplate(P_CLOUDID, cloudId);
Response response = null;
try {
response = target.request().get();
if (response.getStatus() == Response.Status.OK.getStatusCode()) {
return response.readEntity(new GenericType<List<Representation>>() {
}); //formatting here is irreadble
}
ErrorInfo errorInfo = response.readEntity(ErrorInfo.class);
throw MCSExceptionProvider.generateException(errorInfo);
} finally {
closeResponse(response);
}
}
/**
* Returns latest persistent version of representation.
*
* @param cloudId id of record from which to get representations (required)
* @param representationName name of the representation (required)
* @return representation of specified representationName and cloudId
* @throws RepresentationNotExistsException representation does not exist or
* no persistent version of this representation exists
* @throws MCSException on unexpected situations
*/
public Representation getRepresentation(String cloudId, String representationName)
throws RepresentationNotExistsException, MCSException {
WebTarget target = client.target(baseUrl).path(represtationNamePath)
.resolveTemplate(P_CLOUDID, cloudId)
.resolveTemplate(P_REPRESENTATIONNAME, representationName);
Builder request = target.request();
Response response = null;
try {
response = request.get();
if (response.getStatus() == Response.Status.OK.getStatusCode()
|| response.getStatus() == Response.Status.TEMPORARY_REDIRECT.getStatusCode()) {
Representation representation = response.readEntity(Representation.class);
return representation;
} else {
ErrorInfo errorInfo = response.readEntity(ErrorInfo.class);
throw MCSExceptionProvider.generateException(errorInfo);
}
} finally {
closeResponse(response);
}
}
/**
* Creates new representation version.
*
* @param cloudId id of the record in which to create the representation
* (required)
* @param representationName name of the representation to be created
* (required)
* @param providerId provider of this representation version (required)
* @return URI to the created representation
* @throws ProviderNotExistsException when no provider with given id exists
* @throws RecordNotExistsException when cloud id is not known to UIS
* Service
* @throws MCSException on unexpected situations
*/
public URI createRepresentation(String cloudId, String representationName, String providerId)
throws ProviderNotExistsException, RecordNotExistsException, MCSException {
WebTarget target = client.target(baseUrl).path(represtationNamePath)
.resolveTemplate(P_CLOUDID, cloudId)
.resolveTemplate(P_REPRESENTATIONNAME, representationName);
Builder request = target.request();
Form form = new Form();
form.param("providerId", providerId);
Response response = null;
try {
response = request.post(Entity.entity(form, MediaType.APPLICATION_FORM_URLENCODED_TYPE));
if (response.getStatus() == Response.Status.CREATED.getStatusCode()) {
URI uri = response.getLocation();
return uri;
} else {
ErrorInfo errorInfo = response.readEntity(ErrorInfo.class);
throw MCSExceptionProvider.generateException(errorInfo);
}
} finally {
closeResponse(response);
}
}
/**
* Creates new representation version, aploads a file and makes this representation persistent (in one request)
*
* @param cloudId id of the record in which to create the representation
* (required)
* @param representationName name of the representation to be created
* (required)
* @param providerId provider of this representation version (required)
* @param data file that should be uploaded (required)
* @param fileName name for created file
* @param mediaType mimeType of uploaded file
* @return URI to created file
*/
public URI createRepresentation(String cloudId,
String representationName,
String providerId,
InputStream data,
String fileName,
String mediaType) throws MCSException {
WebTarget target = client.target(baseUrl).path(represtationNamePath + "/files")
.resolveTemplate(P_CLOUDID, cloudId)
.resolveTemplate(P_REPRESENTATIONNAME, representationName);
Builder request = target.request();
FormDataMultiPart multipart = prepareRequestBody(providerId, data, fileName, mediaType);
Response response = null;
request.header("Content-Type", "multipart/form-data");
try {
response = request.post(Entity.entity(multipart, MediaType.MULTIPART_FORM_DATA));
if (response.getStatus() == Response.Status.CREATED.getStatusCode()) {
URI uri = response.getLocation();
return uri;
} else {
ErrorInfo errorInfo = response.readEntity(ErrorInfo.class);
throw MCSExceptionProvider.generateException(errorInfo);
}
} finally {
closeResponse(response);
}
}
/**
* Creates new representation version, aploads a file and makes this representation persistent (in one request)
*
* @param cloudId id of the record in which to create the representation
* (required)
* @param representationName name of the representation to be created
* (required)
* @param providerId provider of this representation version (required)
* @param data file that should be uploaded (required)
* @param mediaType mimeType of uploaded file
* @return URI to created file
* @throws MCSException
*/
public URI createRepresentation(String cloudId,
String representationName,
String providerId,
InputStream data,
String mediaType) throws MCSException {
return this.createRepresentation(cloudId, representationName, providerId, data, null, mediaType);
}
private FormDataMultiPart prepareRequestBody(String providerId, InputStream data, String fileName, String mediaType) {
FormDataMultiPart requestBody = new FormDataMultiPart()
.field(ParamConstants.F_PROVIDER, providerId)
.field(ParamConstants.F_FILE_DATA, data, MediaType.APPLICATION_OCTET_STREAM_TYPE)
.field(ParamConstants.F_FILE_MIME, mediaType);
if (fileName != null && !"".equals(fileName.trim())) {
requestBody.field(ParamConstants.F_FILE_NAME, fileName);
}
return requestBody;
}
/**
* Deletes representation with all versions.
*
* @param cloudId id of the record to delete representation from (required)
* @param representationName representation name of deleted representation
* (required)
* @throws RepresentationNotExistsException if specified Representation does
* not exist
* @throws MCSException on unexpected situations
*/
public void deleteRepresentation(String cloudId, String representationName)
throws RepresentationNotExistsException, MCSException {
WebTarget target = client.target(baseUrl).path(represtationNamePath)
.resolveTemplate(P_CLOUDID, cloudId)
.resolveTemplate(P_REPRESENTATIONNAME, representationName);
Builder request = target.request();
Response response = null;
try {
response = request.delete();
if (response.getStatus() != Response.Status.NO_CONTENT.getStatusCode()) {
ErrorInfo errorInfo = response.readEntity(ErrorInfo.class);
throw MCSExceptionProvider.generateException(errorInfo);
}
} finally {
closeResponse(response);
}
}
/**
* Lists all versions of record representation.
*
* @param cloudId id of the record to get representation from (required)
* @param representationName name of the representation (required)
* @return representation versions list
* @throws RepresentationNotExistsException if specified Representation does
* not exist
* @throws MCSException on unexpected situations
*/
public List<Representation> getRepresentations(String cloudId, String representationName)
throws RepresentationNotExistsException, MCSException {
WebTarget target = client.target(baseUrl).path(versionsPath).resolveTemplate(P_CLOUDID, cloudId)
.resolveTemplate(P_REPRESENTATIONNAME, representationName);
Builder request = target.request();
Response response = null;
try {
response = request.get();
if (response.getStatus() == Response.Status.OK.getStatusCode()) {
List<Representation> list = response.readEntity(new GenericType<List<Representation>>() {
});
return list;
} else {
ErrorInfo errorInfo = response.readEntity(ErrorInfo.class);
throw MCSExceptionProvider.generateException(errorInfo);
}
} finally {
closeResponse(response);
}
}
/**
* Returns representation in specified version.
* <p/>
* If Version = LATEST, will redirect to actual latest persistent version at
* the moment of invoking this method.
*
* @param cloudId id of the record to get representation from (required)
* @param representationName name of the representation (required)
* @param version version of the representation to be obtained; if
* version==LATEST function will return latest persistent version (required)
* @return requested representation version
* @throws RepresentationNotExistsException if specified representation does
* not exist
* @throws MCSException on unexpected situations
*/
public Representation getRepresentation(String cloudId, String representationName, String version)
throws RepresentationNotExistsException, MCSException {
WebTarget webtarget = client.target(baseUrl).path(versionPath)
.resolveTemplate(P_CLOUDID, cloudId)
.resolveTemplate(P_REPRESENTATIONNAME, representationName)
.resolveTemplate(ParamConstants.P_VER, version);
Builder request = webtarget.request();
Response response = null;
try {
response = request.get();
System.out.println(response);
if (response.getStatus() == Response.Status.OK.getStatusCode()) {
Representation representation = response.readEntity(Representation.class);
return representation;
} else {
ErrorInfo errorInfo = response.readEntity(ErrorInfo.class);
throw MCSExceptionProvider.generateException(errorInfo);
}
} catch (MessageBodyProviderNotFoundException e) {
String out = webtarget.getUri().toString();
throw new MCSException(out, e);
} finally {
closeResponse(response);
}
}
/**
* Deletes representation in specified version.
*
* @param cloudId id of the record to delete representation version from
* (required)
* @param representationName name of the representation (required)
* @param version the deleted version of the representation (required)
* @throws RepresentationNotExistsException if specified representation does
* not exist
* @throws CannotModifyPersistentRepresentationException if specified
* representation is persistent and thus cannot be removed
* @throws MCSException on unexpected situations
*/
public void deleteRepresentation(String cloudId, String representationName, String version)
throws RepresentationNotExistsException, CannotModifyPersistentRepresentationException, MCSException {
WebTarget webtarget = client.target(baseUrl).path(versionPath)
.resolveTemplate(P_CLOUDID, cloudId)
.resolveTemplate(P_REPRESENTATIONNAME, representationName)
.resolveTemplate(ParamConstants.P_VER, version);
Builder request = webtarget.request();
Response response = null;
try {
response = request.delete();
if (response.getStatus() != Response.Status.NO_CONTENT.getStatusCode()) {
ErrorInfo errorInfo = response.readEntity(ErrorInfo.class);
throw MCSExceptionProvider.generateException(errorInfo);
}
} finally {
closeResponse(response);
}
}
/**
* Copies all information from one representation version to another.
* <p/>
* Copies all information with all files and their content from one
* representation version to a new temporary one.
*
* @param cloudId id of the record that holds representation (required)
* @param representationName name of the copied representation (required)
* @param version version of the copied representation (required)
* @return URI to the created copy of representation
* @throws RepresentationNotExistsException if specified representation
* version does not exist
* @throws MCSException on unexpected situations
*/
public URI copyRepresentation(String cloudId, String representationName, String version)
throws RepresentationNotExistsException, MCSException {
WebTarget target = client.target(baseUrl).path(copyPath).resolveTemplate(P_CLOUDID, cloudId)
.resolveTemplate(P_REPRESENTATIONNAME, representationName)
.resolveTemplate(ParamConstants.P_VER, version);
Builder request = target.request();
Response response = null;
try {
response = request.post(Entity.entity(new Form(), MediaType.APPLICATION_FORM_URLENCODED_TYPE));
if (response.getStatus() == Response.Status.CREATED.getStatusCode()) {
return response.getLocation();
} else {
ErrorInfo errorInfo = response.readEntity(ErrorInfo.class);
throw MCSExceptionProvider.generateException(errorInfo);
}
} finally {
closeResponse(response);
}
}
/**
* Makes specified temporary representation version persistent.
*
* @param cloudId id of the record that holds representation (required)
* @param representationName name of the representation to be persisted
* (required)
* @param version version that should be made persistent (required)
* @return URI to the persisted representation
* @throws RepresentationNotExistsException when representation does not
* exist in specified version
* @throws CannotModifyPersistentRepresentationException when representation
* version is already persistent
* @throws CannotPersistEmptyRepresentationException when representation
* version has no file attached and thus cannot be made persistent
* @throws MCSException on unexpected situations
*/
public URI persistRepresentation(String cloudId, String representationName, String version)
throws RepresentationNotExistsException, CannotModifyPersistentRepresentationException,
CannotPersistEmptyRepresentationException, MCSException {
WebTarget target = client.target(baseUrl).path(persistPath).resolveTemplate(P_CLOUDID, cloudId)
.resolveTemplate(P_REPRESENTATIONNAME, representationName)
.resolveTemplate(ParamConstants.P_VER, version);
Form form = new Form();
Builder request = target.request();
Response response = null;
try {
response = request.post(Entity.entity(form, MediaType.APPLICATION_FORM_URLENCODED_TYPE));
if (response.getStatus() == Response.Status.CREATED.getStatusCode()) {
URI uri = response.getLocation();
return uri;
} else {
ErrorInfo errorInfo = response.readEntity(ErrorInfo.class);
throw MCSExceptionProvider.generateException(errorInfo);
}
} finally {
closeResponse(response);
}
}
/**
* Adds selected permission(s) to selected representation version.
*
* @param cloudId record identifier
* @param representationName representation name
* @param version representation version
* @param userName user who will get access to representation version
* @param permission permission that will be granted
* @throws MCSException
*/
public void grantPermissionsToVersion(String cloudId, String representationName, String version, String userName, Permission permission) throws MCSException {
WebTarget target = client.target(baseUrl).path(grantingPermissionsToVesionPath)
.resolveTemplate(P_CLOUDID, cloudId)
.resolveTemplate(P_REPRESENTATIONNAME, representationName)
.resolveTemplate(ParamConstants.P_VER, version)
.resolveTemplate(ParamConstants.P_PERMISSION_TYPE, permission.getValue())
.resolveTemplate(ParamConstants.P_USERNAME, userName);
Builder request = target.request();
Response response = null;
try {
response = request.post(null);
if (response.getStatus() != Response.Status.OK.getStatusCode()) {
throwException(response);
}
} finally {
closeResponse(response);
}
}
/**
* Revokes permission(s) to selected representation version.
*
* @param cloudId record identifier
* @param representationName representation name
* @param version representation version
* @param userName user who will get access to representation version
* @param permission permission that will be granted
* @throws MCSException
*/
public void revokePermissionsToVersion(String cloudId, String representationName, String version, String userName, Permission permission) throws MCSException {
WebTarget target = client.target(baseUrl).path(grantingPermissionsToVesionPath)
.resolveTemplate(P_CLOUDID, cloudId)
.resolveTemplate(P_REPRESENTATIONNAME, representationName)
.resolveTemplate(ParamConstants.P_VER, version)
.resolveTemplate(ParamConstants.P_PERMISSION_TYPE, permission.getValue())
.resolveTemplate(ParamConstants.P_USERNAME, userName);
Builder request = target.request();
Response response = null;
try {
response = request.delete();
if (response.getStatus() != Response.Status.NO_CONTENT.getStatusCode()) {
ErrorInfo errorInfo = response.readEntity(ErrorInfo.class);
throw MCSExceptionProvider.generateException(errorInfo);
}
} finally {
closeResponse(response);
}
}
/**
* Adds selected permission(s) to selected representation version.
*
* @param cloudId record identifier
* @param representationName representation name
* @param version representation version
* @throws MCSException
*/
public void permitVersion(String cloudId, String representationName, String version) throws MCSException {
WebTarget target = client.target(baseUrl).path(permitPath)
.resolveTemplate(P_CLOUDID, cloudId)
.resolveTemplate(P_REPRESENTATIONNAME, representationName)
.resolveTemplate(ParamConstants.P_VER, version);
Builder request = target.request();
Response response = null;
try {
response = request.post(null);
if (response.getStatus() != Response.Status.OK.getStatusCode()) {
throwException(response);
}
} finally {
closeResponse(response);
}
}
private void throwException(Response response) throws MCSException {
try {
ErrorInfo errorInfo = response.readEntity(ErrorInfo.class);
throw MCSExceptionProvider.generateException(errorInfo);
} catch (MessageBodyProviderNotFoundException e) {
ErrorInfo errorInfo = new ErrorInfo();
errorInfo.setErrorCode(McsErrorCode.OTHER.toString());
errorInfo.setDetails("Mcs not available");
throw MCSExceptionProvider.generateException(errorInfo);
}
}
private void closeResponse(Response response) {
if (response != null) {
response.close();
}
}
@Override
protected void finalize() throws Throwable {
client.close();
}
/**
* Returns representation in specified version.
* <p/>
* If Version = LATEST, will redirect to actual latest persistent version at
* the moment of invoking this method.
*
* @param cloudId id of the record to get representation from (required)
* @param representationName name of the representation (required)
* @param revisionName revision name (required)
* @param revisionProviderId revision provider identifier, together with revisionId it is used to determine the correct revision (required)
* @return requested representation version
* @throws RepresentationNotExistsException if specified representation does
* not exist
* @throws MCSException on unexpected situations
*/
public RepresentationRevisionResponse getRepresentationRevision(String cloudId, String representationName, String revisionName, String revisionProviderId, String revisionTimestamp)
throws RevisionNotExistsException, MCSException {
WebTarget webtarget = client.target(baseUrl).path(representationsRevisionsPath)
.resolveTemplate(P_CLOUDID, cloudId)
.resolveTemplate(P_REPRESENTATIONNAME, representationName)
.resolveTemplate(P_REVISION_NAME, revisionName);
if (revisionProviderId != null) {
webtarget = webtarget.queryParam(F_REVISION_PROVIDER_ID, revisionProviderId);
} else
throw new MCSException("RevisionProviderId is required");
// revision timestamp is optional
if (revisionTimestamp != null)
webtarget = webtarget.queryParam(F_REVISION_TIMESTAMP, revisionTimestamp);
Builder request = webtarget.request();
Response response = null;
try {
response = request.get();
if (response.getStatus() == Response.Status.OK.getStatusCode()) {
RepresentationRevisionResponse representationRevisionResponse = response.readEntity(RepresentationRevisionResponse.class);
return representationRevisionResponse;
} else {
ErrorInfo errorInfo = response.readEntity(ErrorInfo.class);
throw MCSExceptionProvider.generateException(errorInfo);
}
} catch (MessageBodyProviderNotFoundException e) {
String out = webtarget.getUri().toString();
throw new MCSException(out, e);
} finally {
closeResponse(response);
}
}
}