package eu.europeana.cloud.service.uis.rest; import com.qmino.miredot.annotations.ReturnType; import eu.europeana.cloud.common.exceptions.ProviderDoesNotExistException; import eu.europeana.cloud.common.model.CloudId; import eu.europeana.cloud.common.model.DataProvider; import eu.europeana.cloud.common.model.DataProviderProperties; import eu.europeana.cloud.common.response.ResultSlice; import eu.europeana.cloud.common.web.UISParamConstants; import eu.europeana.cloud.service.aas.authentication.SpringUserUtils; import eu.europeana.cloud.service.uis.DataProviderService; import eu.europeana.cloud.service.uis.UniqueIdentifierService; import eu.europeana.cloud.service.uis.exception.CloudIdAlreadyExistException; import eu.europeana.cloud.service.uis.exception.CloudIdDoesNotExistException; import eu.europeana.cloud.service.uis.exception.DatabaseConnectionException; import eu.europeana.cloud.service.uis.exception.IdHasBeenMappedException; import eu.europeana.cloud.service.uis.exception.RecordDatasetEmptyException; import eu.europeana.cloud.service.uis.exception.RecordIdDoesNotExistException; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Scope; import org.springframework.security.access.prepost.PreAuthorize; import org.springframework.security.acls.domain.BasePermission; import org.springframework.security.acls.domain.ObjectIdentityImpl; import org.springframework.security.acls.domain.PrincipalSid; import org.springframework.security.acls.model.MutableAcl; import org.springframework.security.acls.model.MutableAclService; import org.springframework.security.acls.model.ObjectIdentity; import org.springframework.stereotype.Component; import javax.validation.constraints.Max; import javax.validation.constraints.Min; import javax.ws.rs.Consumes; import javax.ws.rs.DELETE; import javax.ws.rs.DefaultValue; 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.Produces; import javax.ws.rs.QueryParam; import javax.ws.rs.core.Context; import javax.ws.rs.core.MediaType; import javax.ws.rs.core.Response; import javax.ws.rs.core.UriInfo; import static eu.europeana.cloud.common.web.ParamConstants.*; import org.springframework.security.acls.model.AlreadyExistsException; import org.springframework.security.acls.model.ChildrenExistException; import org.springframework.security.acls.model.NotFoundException; import java.util.List; /** * Resource for DataProvider. * * @author */ @Path("/data-providers/{" + P_PROVIDER + "}") @Component @Scope("request") public class DataProviderResource { @Autowired private UniqueIdentifierService uniqueIdentifierService; @Autowired private DataProviderService providerService; @Autowired private MutableAclService mutableAclService; protected final String LOCAL_ID_CLASS_NAME = "LocalId"; /** * Retrieves details about selected data provider * * @summary Data provider details retrieval * * * @param providerId * <strong>REQUIRED</strong> identifier of the provider that will * be retrieved * * @return Selected Data provider details * * @throws ProviderDoesNotExistException * The supplied provider does not exist */ @GET @Produces({ MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON }) public DataProvider getProvider(@PathParam(P_PROVIDER) String providerId) throws ProviderDoesNotExistException { return providerService.getProvider(providerId); } /** * Updates data provider information. * * <br/> * <br/> * <div style='border-left: solid 5px #999999; border-radius: 10px; padding: * 6px;'> <strong>Required permissions:</strong> * <ul> * <li>Authenticated user</li> * <li>Write permission for the selected data provider</li> * </ul> * </div> * * @summary Data provider information update * * @param dataProviderProperties * <strong>REQUIRED</strong> data provider properties. * * @param providerId * <strong>REQUIRED</strong> identifier of data provider which * will be updated. * * @statuscode 204 object has been updated. * * @throws ProviderDoesNotExistException * The supplied provider does not exist */ @PUT @Consumes({ MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON }) @PreAuthorize("hasPermission(#providerId, 'eu.europeana.cloud.common.model.DataProvider', write)") public void updateProvider(DataProviderProperties dataProviderProperties, @PathParam(P_PROVIDER) String providerId, @Context UriInfo uriInfo) throws ProviderDoesNotExistException { DataProvider provider = providerService.updateProvider(providerId, dataProviderProperties); EnrichUriUtil.enrich(uriInfo, provider); } /** * Deletes data provider from database * * <br/> * <br/> * <div style='border-left: solid 5px #999999; border-radius: 10px; padding: * 6px;'> <strong>Required permissions:</strong> * <ul> * <li>Admin role</li> * </ul> * </div> * * @summary Data provider deletion * * @param dataProviderId * <strong>REQUIRED</strong> data provider id * * @return Empty response with http status code indicating whether the * operation was successful or not * * @throws ProviderDoesNotExistException * The supplied provider does not exist * */ @DELETE @PreAuthorize("hasRole('ROLE_ADMIN')") public Response deleteProvider(@PathParam(P_PROVIDER) String dataProviderId) throws ProviderDoesNotExistException { providerService.deleteProvider(dataProviderId); deleteProviderAcl(dataProviderId); return Response.ok().build(); } /** * * Get the local Identifiers (with their cloud identifiers) for a specific provider identifier with * pagination * * @summary Local identifiers retrieval. * * @param providerId * <strong>REQUIRED</strong> identifier of provider for which all * local identifiers will be retrieved * @param from * from which one local identifier should we start. * @param to * how many local identifiers should be contained in results * list. Default is 10000 * * @return A list of local Identifiers (with their cloud identifiers) * * @throws DatabaseConnectionException * database error * @throws ProviderDoesNotExistException * provider does not exist * @throws RecordDatasetEmptyException * dataset is empty * */ @GET @Path("localIds") @Produces({ MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML }) @ReturnType("eu.europeana.cloud.common.response.ResultSlice<eu.europeana.cloud.common.model.CloudId>") public Response getLocalIdsByProvider(@PathParam(P_PROVIDER) String providerId, @QueryParam(UISParamConstants.Q_FROM) String from, @QueryParam(UISParamConstants.Q_LIMIT) @DefaultValue("10000") int to) throws DatabaseConnectionException, ProviderDoesNotExistException, RecordDatasetEmptyException { ResultSlice<CloudId> pList = new ResultSlice<>(); pList.setResults(uniqueIdentifierService.getLocalIdsByProvider(providerId, from, to)); if (pList.getResults().size() == to) { pList.setNextSlice(pList.getResults().get(to - 1).getId()); } return Response.ok(pList).build(); } /** * * Get the cloud identifiers (with their local identifiers) for a specific provider identifier with * pagination * * @summary Cloud identifiers (with their local identifiers) retrieval. * * @param providerId * <strong>REQUIRED</strong> identifier of provider for which all * record identifiers will be retrieved * @param from * from which one <strong>local identifier</strong> should we start. * @param limit * how many cloud identifiers should be contained in results * list. Default is 10000. <strong>Respected only if {@code from} parameter is defined.</strong> * * @return List of cloud identifiers (with their local identifiers) for specific provider * * @throws DatabaseConnectionException * database connection errot * @throws ProviderDoesNotExistException * provider does not exist * @throws RecordDatasetEmptyException * record dataset is empty */ @GET @Path("cloudIds") @Produces({ MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML }) @ReturnType("eu.europeana.cloud.common.response.ResultSlice<eu.europeana.cloud.common.model.CloudId>") public Response getCloudIdsByProvider(@PathParam(P_PROVIDER) String providerId, @QueryParam(UISParamConstants.Q_FROM) String from, @Min(0) @Max(10000) @QueryParam(UISParamConstants.Q_LIMIT) @DefaultValue("10000") int limit) throws DatabaseConnectionException, ProviderDoesNotExistException, RecordDatasetEmptyException { ResultSlice<CloudId> slice = new ResultSlice<>(); final int limitWithNextSlice = limit + 1; final List<CloudId> cloudIds = uniqueIdentifierService.getCloudIdsByProvider(providerId, from, limitWithNextSlice); if (cloudIds.size() == limitWithNextSlice) { setNextSliceAndRemoveLastElement(slice, limitWithNextSlice, cloudIds); } slice.setResults(cloudIds); return Response.ok(slice).build(); } private void setNextSliceAndRemoveLastElement(ResultSlice<CloudId> slice, int limitWithNextSlice, List<CloudId> cloudIdsByProvider) { CloudId nextSlice = cloudIdsByProvider.remove(limitWithNextSlice - 1); slice.setNextSlice(nextSlice.getLocalId().getRecordId()); } /** * * Create a mapping between a cloud identifier and a record identifier for a * provider * * <br/> * <br/> * <div style='border-left: solid 5px #999999; border-radius: 10px; padding: * 6px;'> <strong>Required permissions:</strong> * <ul> * <li>Authenticated user</li> * </ul> * </div> * * @summary Cloud identifier to record identifier mapping creation * * @param providerId * <strong>REQUIRED</strong> identifier of data provider, owner * of the record * @param cloudId * <strong>REQUIRED</strong> cloud identifier for which new * record identifier will be added * @param localId * record identifier which will be bound to selected cloud * identifier. If not specified, random one will be generated * * @return The newly associated cloud identifier * * @throws DatabaseConnectionException * datbase connection error * @throws CloudIdDoesNotExistException * cloud identifier does not exist * @throws IdHasBeenMappedException * identifier already mapped * @throws ProviderDoesNotExistException * provider does not exist * @throws RecordDatasetEmptyException * empty dataset * @throws CloudIdAlreadyExistException * cloud identifier alrrasy exist * */ @POST @Path("cloudIds/{" + P_CLOUDID + "}") @Produces({ MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML }) @PreAuthorize("isAuthenticated()") @ReturnType("eu.europeana.cloud.common.model.CloudId") public Response createIdMapping(@PathParam(P_PROVIDER) String providerId, @PathParam(P_CLOUDID) String cloudId, @QueryParam(UISParamConstants.Q_RECORD_ID) String localId) throws DatabaseConnectionException, CloudIdDoesNotExistException, IdHasBeenMappedException, ProviderDoesNotExistException, RecordDatasetEmptyException, CloudIdAlreadyExistException { CloudId result = null; if (localId != null) { result = uniqueIdentifierService.createIdMapping(cloudId, providerId, localId); } else { result = uniqueIdentifierService.createIdMapping(cloudId, providerId); } grantPermissionsToLocalId(result, providerId); return Response.ok().entity(result).build(); } protected void grantPermissionsToLocalId(CloudId result, String providerId) throws NotFoundException, AlreadyExistsException { String creatorName = SpringUserUtils.getUsername(); String key = result.getLocalId().getRecordId() + "/" + providerId; if (creatorName != null) { ObjectIdentity providerIdentity = new ObjectIdentityImpl(LOCAL_ID_CLASS_NAME, key); MutableAcl providerAcl = mutableAclService.createAcl(providerIdentity); providerAcl.insertAce(0, BasePermission.READ, new PrincipalSid(creatorName), true); providerAcl.insertAce(1, BasePermission.WRITE, new PrincipalSid(creatorName), true); providerAcl.insertAce(2, BasePermission.DELETE, new PrincipalSid(creatorName), true); providerAcl.insertAce(3, BasePermission.ADMINISTRATION, new PrincipalSid(creatorName), true); mutableAclService.updateAcl(providerAcl); } } /** * * Remove the mapping between a record identifier and a cloud identifier * * <br/> * <br/> * <div style='border-left: solid 5px #999999; border-radius: 10px; padding: * 6px;'> <strong>Required permissions:</strong> * <ul> * <li>Authenticated user</li> * <li>Write permissions for selected data provider and local identifier * (granted at the mapping creation)</li> * </ul> * </div> * * @summary Mapping between record and cloud identifier removal * * @param providerId * <strong>REQUIRED</strong> identifier of the provider, owner of * the record * * @param localId * <strong>REQUIRED</strong> record identifier which will be * detached from selected provider identifier. * * @return Confirmation that the mapping has been removed * * @throws DatabaseConnectionException * database error * @throws ProviderDoesNotExistException * provider does not exist * @throws RecordIdDoesNotExistException * record does not exist */ @DELETE @Path("localIds/{" + P_LOCALID + "}") @Produces({ MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML }) @PreAuthorize("hasPermission(#localId.concat('/').concat(#providerId),'" + LOCAL_ID_CLASS_NAME + "', write)") public Response removeIdMapping(@PathParam(P_PROVIDER) String providerId, @PathParam(P_LOCALID) String localId) throws DatabaseConnectionException, ProviderDoesNotExistException, RecordIdDoesNotExistException { uniqueIdentifierService.removeIdMapping(providerId, localId); deleteLocalIdAcl(localId, providerId); return Response.ok("Mapping marked as deleted").build(); } protected void deleteLocalIdAcl(String localId, String providerId) throws ChildrenExistException { String key = localId + "/" + providerId; ObjectIdentity providerIdentity = new ObjectIdentityImpl(LOCAL_ID_CLASS_NAME, key); mutableAclService.deleteAcl(providerIdentity, false); } private void deleteProviderAcl(String dataProviderId) { ObjectIdentity providerIdentity = new ObjectIdentityImpl(DataProvider.class.getName(), dataProviderId); mutableAclService.deleteAcl(providerIdentity, false); } }