/******************************************************************************* * Copyright French Prime minister Office/SGMAP/DINSIC/Vitam Program (2015-2019) * * contact.vitam@culture.gouv.fr * * This software is a computer program whose purpose is to implement a digital archiving back-office system managing * high volumetry securely and efficiently. * * This software is governed by the CeCILL 2.1 license under French law and abiding by the rules of distribution of free * software. You can use, modify and/ or redistribute the software under the terms of the CeCILL 2.1 license as * circulated by CEA, CNRS and INRIA at the following URL "http://www.cecill.info". * * As a counterpart to the access to the source code and rights to copy, modify and redistribute granted by the license, * users are provided only with a limited warranty and the software's author, the holder of the economic rights, and the * successive licensors have only limited liability. * * In this respect, the user's attention is drawn to the risks associated with loading, using, modifying and/or * developing or reproducing the software by the user in light of its specific status of free software, that may mean * that it is complicated to manipulate, and that also therefore means that it is reserved for developers and * experienced professionals having in-depth computer knowledge. Users are therefore encouraged to load and test the * software's suitability as regards their requirements in conditions enabling the security of their systems and/or data * to be ensured and, more generally, to use and operate it in the same conditions as regards security. * * The fact that you are presently reading this means that you have had knowledge of the CeCILL 2.1 license and that you * accept its terms. *******************************************************************************/ package fr.gouv.vitam.storage.engine.server.rest; import java.io.IOException; import javax.servlet.http.HttpServletRequest; import javax.ws.rs.Consumes; import javax.ws.rs.DELETE; import javax.ws.rs.GET; import javax.ws.rs.HEAD; import javax.ws.rs.HeaderParam; import javax.ws.rs.HttpMethod; 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.container.AsyncResponse; import javax.ws.rs.container.Suspended; import javax.ws.rs.core.Context; import javax.ws.rs.core.HttpHeaders; import javax.ws.rs.core.MediaType; import javax.ws.rs.core.MultivaluedHashMap; import javax.ws.rs.core.Response; import javax.ws.rs.core.Response.Status; import com.fasterxml.jackson.databind.JsonNode; import fr.gouv.vitam.common.CommonMediaType; import fr.gouv.vitam.common.GlobalDataRest; import fr.gouv.vitam.common.ParametersChecker; import fr.gouv.vitam.common.digest.DigestType; import fr.gouv.vitam.common.error.VitamCode; import fr.gouv.vitam.common.error.VitamCodeHelper; import fr.gouv.vitam.common.error.VitamError; import fr.gouv.vitam.common.exception.InvalidParseOperationException; import fr.gouv.vitam.common.logging.VitamLogger; import fr.gouv.vitam.common.logging.VitamLoggerFactory; import fr.gouv.vitam.common.model.VitamAutoCloseable; import fr.gouv.vitam.common.security.SanityChecker; import fr.gouv.vitam.common.server.application.AsyncInputStreamHelper; import fr.gouv.vitam.common.server.application.HttpHeaderHelper; import fr.gouv.vitam.common.server.application.VitamHttpHeader; import fr.gouv.vitam.common.server.application.resources.ApplicationStatusResource; import fr.gouv.vitam.common.thread.VitamThreadPoolExecutor; import fr.gouv.vitam.common.thread.VitamThreadUtils; import fr.gouv.vitam.storage.driver.exception.StorageObjectAlreadyExistsException; import fr.gouv.vitam.storage.engine.common.exception.StorageException; import fr.gouv.vitam.storage.engine.common.exception.StorageNotFoundException; import fr.gouv.vitam.storage.engine.common.model.DataCategory; import fr.gouv.vitam.storage.engine.common.model.request.ObjectDescription; import fr.gouv.vitam.storage.engine.common.model.response.RequestResponseError; import fr.gouv.vitam.storage.engine.common.model.response.StoredInfoResult; import fr.gouv.vitam.storage.engine.server.distribution.StorageDistribution; import fr.gouv.vitam.storage.engine.server.distribution.impl.StorageDistributionImpl; /** * Storage Resource implementation */ @Path("/storage/v1") @javax.ws.rs.ApplicationPath("webresources") public class StorageResource extends ApplicationStatusResource implements VitamAutoCloseable { private static final VitamLogger LOGGER = VitamLoggerFactory.getInstance(StorageResource.class); private static final String STORAGE_MODULE = "STORAGE"; private static final String CODE_VITAM = "code_vitam"; private final StorageDistribution distribution; /** * Constructor * * @param configuration * the storage configuration to be applied */ public StorageResource(StorageConfiguration configuration) { distribution = new StorageDistributionImpl(configuration); LOGGER.info("init Storage Resource server"); } /** * Constructor used for test purpose * * @param storageDistribution * the storage Distribution to be applied */ StorageResource(StorageDistribution storageDistribution) { distribution = storageDistribution; } /** * @param headers * http headers * @return null if strategy and tenant headers have values, an error * response otherwise */ private Response checkTenantStrategyHeader(HttpHeaders headers) { if (!HttpHeaderHelper.hasValuesFor(headers, VitamHttpHeader.TENANT_ID) || !HttpHeaderHelper.hasValuesFor(headers, VitamHttpHeader.STRATEGY_ID)) { return buildErrorResponse(VitamCode.STORAGE_MISSING_HEADER); } return null; } /** * * @param strategyId * StrategyId if directly getting from Header parameter * @return null if strategy and tenant headers have values, an error * response otherwise */ private Response checkTenantStrategyHeader(String strategyId) { if (VitamThreadUtils.getVitamSession().getTenantId() == null) { return buildErrorResponse(VitamCode.STORAGE_MISSING_HEADER); } try { SanityChecker.checkParameter(strategyId); } catch (InvalidParseOperationException e) { LOGGER.error("Missing Strategy", e); return buildErrorResponse(VitamCode.STORAGE_MISSING_HEADER); } return null; } /** * @param headers * http headers * @return null if strategy, tenant, digest and digest algorithm headers * have values, an error response otherwise */ private Response checkDigestAlgorithmHeader(HttpHeaders headers) { if (!HttpHeaderHelper.hasValuesFor(headers, VitamHttpHeader.TENANT_ID) || !HttpHeaderHelper.hasValuesFor(headers, VitamHttpHeader.STRATEGY_ID) || !HttpHeaderHelper.hasValuesFor(headers, VitamHttpHeader.X_DIGEST) || !HttpHeaderHelper.hasValuesFor(headers, VitamHttpHeader.X_DIGEST_ALGORITHM)) { return buildErrorResponse(VitamCode.STORAGE_MISSING_HEADER); } return null; } /** * Get storage information for a specific tenant/strategy For example the * usable space * * @param headers * http headers * @return Response containing the storage information as json, or an error * (404, 500) */ @HEAD @Consumes(MediaType.APPLICATION_JSON) @Produces(MediaType.APPLICATION_JSON) public Response getStorageInformation(@Context HttpHeaders headers) { final Response response = checkTenantStrategyHeader(headers); if (response == null) { VitamCode vitamCode; final String strategyId = HttpHeaderHelper.getHeaderValues(headers, VitamHttpHeader.STRATEGY_ID).get(0); try { final JsonNode result = distribution.getContainerInformation(strategyId); return Response.status(Status.OK).entity(result).build(); } catch (final StorageNotFoundException exc) { LOGGER.error(exc); vitamCode = VitamCode.STORAGE_NOT_FOUND; } catch (final StorageException exc) { LOGGER.error(exc); vitamCode = VitamCode.STORAGE_TECHNICAL_INTERNAL_ERROR; } return buildErrorResponse(vitamCode); } return response; } /** * Search the header value for 'X-Http-Method-Override' and return an error * response id it's value is not 'GET' * * @param headers * the http headers to check * @return OK response if no header is found, NULL if header value is * correct, BAD_REQUEST if the header contain an other value than * GET */ public Response checkPostHeader(HttpHeaders headers) { if (HttpHeaderHelper.hasValuesFor(headers, VitamHttpHeader.METHOD_OVERRIDE)) { final MultivaluedHashMap<String, String> wanted = new MultivaluedHashMap<>(); wanted.add(VitamHttpHeader.METHOD_OVERRIDE.getName(), HttpMethod.GET); try { HttpHeaderHelper.validateHeaderValue(headers, wanted); return null; } catch (IllegalArgumentException | IllegalStateException exc) { LOGGER.error(exc); return badRequestResponse(exc.getMessage()); } } else { return Response.status(Status.OK).build(); } } /** * Create a container * <p> * * @param headers * http header * @return Response NOT_IMPLEMENTED */ // TODO P1 : container creation possibility needs to be re-think then // deleted or implemented. Vitam Architects are // aware of this @POST @Produces(MediaType.APPLICATION_JSON) @Consumes(MediaType.APPLICATION_JSON) public Response createContainer(@Context HttpHeaders headers) { Response response = checkTenantStrategyHeader(headers); if (response != null) { return response; } final Status status = Status.NOT_IMPLEMENTED; return Response.status(status).entity(getErrorEntity(status)).build(); } /** * Delete a container * * Note : this is NOT to be handled in item #72. * * @param headers * http header * @return Response NOT_IMPLEMENTED */ @DELETE @Produces(MediaType.APPLICATION_JSON) @Consumes(MediaType.APPLICATION_JSON) public Response deleteContainer(@Context HttpHeaders headers) { Response response = checkTenantStrategyHeader(headers); if (response != null) { return response; } final Status status = Status.NOT_IMPLEMENTED; return Response.status(status).entity(getErrorEntity(status)).build(); } /** * Get list of object type * * @param xcursor * the X-Cursor * @param xcursorId * the X-Cursor-Id if exists * @param strategyId * the strategy to get offers * @param type * the object type to list * @return a response with listing elements */ @Path("/{type}") @GET @Produces(MediaType.APPLICATION_JSON) @Consumes(MediaType.APPLICATION_JSON) public Response listObjects(@HeaderParam(GlobalDataRest.X_CURSOR) boolean xcursor, @HeaderParam(GlobalDataRest.X_CURSOR_ID) String xcursorId, @HeaderParam(GlobalDataRest.X_STRATEGY_ID) String strategyId, @PathParam("type") DataCategory type) { final Response response = checkTenantStrategyHeader(strategyId); if (response != null) { return response; } try { ParametersChecker.checkParameter("X-Cursor is required", xcursor); ParametersChecker.checkParameter("Strategy ID is required", strategyId); return distribution.listContainerObjects(strategyId, type, xcursorId); } catch (IllegalArgumentException exc) { LOGGER.error(exc); return buildErrorResponse(VitamCode.STORAGE_MISSING_HEADER); } catch (Exception exc) { LOGGER.error(exc); return buildErrorResponse(VitamCode.STORAGE_TECHNICAL_INTERNAL_ERROR); } } /** * Get object metadata as json Note : this is NOT to be handled in item #72. * * @param headers * http header * @param objectId * the id of the object * @return Response NOT_IMPLEMENTED */ @Path("/objects/{id_object}") @GET @Produces(MediaType.APPLICATION_JSON) @Consumes(MediaType.APPLICATION_JSON) public Response getObjectInformation(@Context HttpHeaders headers, @PathParam("id_object") String objectId) { Response response = checkTenantStrategyHeader(headers); if (response != null) { return response; } final Status status = Status.NOT_IMPLEMENTED; return Response.status(status).entity(getErrorEntity(status)).build(); } /** * Get an object data Note : this is NOT to be handled in item #72. * * @param headers * http header * @param objectId * the id of the object * @param asyncResponse * async response * @throws IOException * throws an IO Exception */ @Path("/objects/{id_object}") @GET @Produces({ MediaType.APPLICATION_OCTET_STREAM, CommonMediaType.ZIP }) public void getObject(@Context HttpHeaders headers, @PathParam("id_object") String objectId, @Suspended final AsyncResponse asyncResponse) throws IOException { VitamThreadPoolExecutor.getDefaultExecutor().execute(new Runnable() { @Override public void run() { getByCategoryAsync(objectId, headers, DataCategory.OBJECT, asyncResponse); } }); } private void getByCategoryAsync(String objectId, HttpHeaders headers, DataCategory category, AsyncResponse asyncResponse) { VitamCode vitamCode = checkTenantStrategyHeaderAsync(headers); if (vitamCode == null) { final String strategyId = HttpHeaderHelper.getHeaderValues(headers, VitamHttpHeader.STRATEGY_ID).get(0); try { distribution.getContainerByCategory(strategyId, objectId, category, asyncResponse); return; } catch (final StorageNotFoundException exc) { LOGGER.error(exc); vitamCode = VitamCode.STORAGE_NOT_FOUND; } catch (final StorageException exc) { LOGGER.error(exc); vitamCode = VitamCode.STORAGE_TECHNICAL_INTERNAL_ERROR; } } if (vitamCode != null) { buildErrorResponseAsync(vitamCode, asyncResponse); } } /** * Post a new object * * @param httpServletRequest * http servlet request to get requester * @param headers * http header * @param objectId * the id of the object * @param createObjectDescription * the object description * @return Response response */ // TODO P1 : remove httpServletRequest when requester information sent by // header (X-Requester) @Path("/objects/{id_object}") @POST @Produces(MediaType.APPLICATION_JSON) @Consumes(MediaType.APPLICATION_JSON) public Response createObjectOrGetInformation(@Context HttpServletRequest httpServletRequest, @Context HttpHeaders headers, @PathParam("id_object") String objectId, ObjectDescription createObjectDescription) { // If the POST is a creation request if (createObjectDescription != null) { // TODO P1 : actually no X-Requester header, so send the // getRemoteAdr from HttpServletRequest return createObjectByType(headers, objectId, createObjectDescription, DataCategory.OBJECT, httpServletRequest.getRemoteAddr()); } else { return getObjectInformationWithPost(headers, objectId); } } private Response getObjectInformationWithPost(HttpHeaders headers, String objectId) { final Response response = checkTenantStrategyHeader(headers); if (response != null) { return response; } final Response responsePost = checkPostHeader(headers); if (responsePost == null) { return getObjectInformation(headers, objectId); } else if (responsePost.getStatus() == Status.OK.getStatusCode()) { return Response.status(Status.PRECONDITION_FAILED).build(); } else { return responsePost; } } /** * Delete an object * * @param headers * http header * @param objectId * the id of the object * @return Response NOT_IMPLEMENTED */ @Path("/objects/{id_object}") @DELETE @Produces(MediaType.APPLICATION_JSON) @Consumes(MediaType.APPLICATION_JSON) public Response deleteObject(@Context HttpHeaders headers, @PathParam("id_object") String objectId) { String strategyId, digestAlgorithm, digest; Response response = checkTenantStrategyHeader(headers); if (response != null) { return response; } response = checkDigestAlgorithmHeader(headers); if (response == null) { strategyId = HttpHeaderHelper.getHeaderValues(headers, VitamHttpHeader.STRATEGY_ID).get(0); digest = HttpHeaderHelper.getHeaderValues(headers, VitamHttpHeader.X_DIGEST).get(0); digestAlgorithm = HttpHeaderHelper.getHeaderValues(headers, VitamHttpHeader.X_DIGEST_ALGORITHM).get(0); try { distribution.deleteObject(strategyId, objectId, digest, DigestType.fromValue(digestAlgorithm)); return Response.status(Status.NO_CONTENT).build(); } catch (final StorageException e) { LOGGER.error(e); return buildErrorResponse(VitamCode.STORAGE_NOT_FOUND); } } return response; } /** * Check the existence of an object * * @param headers * http header * @param objectId * the id of the object * @return Response NOT_IMPLEMENTED */ @Path("/objects/{id_object}") @HEAD @Produces(MediaType.APPLICATION_JSON) @Consumes(MediaType.APPLICATION_JSON) public Response checkObject(@Context HttpHeaders headers, @PathParam("id_object") String objectId) { String strategyId; final Response response = checkTenantStrategyHeader(headers); if (response == null) { strategyId = HttpHeaderHelper.getHeaderValues(headers, VitamHttpHeader.STRATEGY_ID).get(0); try { distribution.getContainerObjectInformations(strategyId, objectId); return Response.status(Status.OK).build(); } catch (final StorageNotFoundException e) { LOGGER.error(e); return buildErrorResponse(VitamCode.STORAGE_NOT_FOUND); } } return response; } /** * Get a list of logbooks * <p> * Note : this is NOT to be handled in item #72. * * @param headers * http header * @return Response NOT_IMPLEMENTED */ @Path("/logbooks") @GET @Produces(MediaType.APPLICATION_JSON) @Consumes(MediaType.APPLICATION_JSON) public Response getLogbooks(@Context HttpHeaders headers) { Response response = checkTenantStrategyHeader(headers); if (response != null) { return response; } final Status status = Status.NOT_IMPLEMENTED; return Response.status(status).entity(getErrorEntity(status)).build(); } /** * Get an object * <p> * Note : this is NOT to be handled in item #72. * * @param headers * http header * @param logbookId * the id of the logbook * @return Response NOT_IMPLEMENTED */ @Path("/logbooks/{id_logbook}") @GET @Produces(MediaType.APPLICATION_JSON) @Consumes(MediaType.APPLICATION_JSON) public Response getLogbook(@Context HttpHeaders headers, @PathParam("id_logbook") String logbookId) { Response response = checkTenantStrategyHeader(headers); if (response != null) { return response; } final Status status = Status.NOT_IMPLEMENTED; return Response.status(status).entity(getErrorEntity(status)).build(); } /** * * @param headers * http header * @param objectId * the id of the object * @param asyncResponse * async response * @throws IOException * exception */ @Path("/logbooks/{id_logbook}") @GET @Produces(MediaType.APPLICATION_OCTET_STREAM) public void getLogBook(@Context HttpHeaders headers, @PathParam("id_logbook") String objectId, @Suspended final AsyncResponse asyncResponse) throws IOException { VitamThreadPoolExecutor.getDefaultExecutor().execute(new Runnable() { @Override public void run() { getByCategoryAsync(objectId, headers, DataCategory.LOGBOOK, asyncResponse); } }); } /** * Post a new object * * @param httpServletRequest * http servlet request to get requester * * @param headers * http header * @param logbookId * the id of the logbookId * @param createObjectDescription * the workspace information about logbook to be created * @return Response NOT_IMPLEMENTED */ // TODO P1: remove httpServletRequest when requester information sent by // header (X-Requester) @Path("/logbooks/{id_logbook}") @POST @Produces(MediaType.APPLICATION_JSON) @Consumes(MediaType.APPLICATION_JSON) public Response createLogbook(@Context HttpServletRequest httpServletRequest, @Context HttpHeaders headers, @PathParam("id_logbook") String logbookId, ObjectDescription createObjectDescription) { if (createObjectDescription == null) { return getLogbook(headers, logbookId); } else { // TODO P1: actually no X-Requester header, so send the getRemoteAdr // from HttpServletRequest return createObjectByType(headers, logbookId, createObjectDescription, DataCategory.LOGBOOK, httpServletRequest.getRemoteAddr()); } } /** * Delete a logbook Note : this is NOT to be handled in item #72. * * @param headers * http header * @param logbookId * the id of the logbook * @return Response NOT_IMPLEMENTED */ @Path("/logbooks/{id_logbook}") @DELETE @Produces(MediaType.APPLICATION_JSON) @Consumes(MediaType.APPLICATION_JSON) public Response deleteLogbook(@Context HttpHeaders headers, @PathParam("id_logbook") String logbookId) { Response response = checkTenantStrategyHeader(headers); if (response != null) { return response; } final Status status = Status.NOT_IMPLEMENTED; return Response.status(status).entity(getErrorEntity(status)).build(); } /** * Check the existence of a logbook Note : this is NOT to be handled in item * #72. * * @param headers * http header * @param logbookId * the id of the logbook * @return Response NOT_IMPLEMENTED */ @Path("/logbooks/{id_logbook}") @HEAD @Produces(MediaType.APPLICATION_JSON) @Consumes(MediaType.APPLICATION_JSON) public Response checkLogbook(@Context HttpHeaders headers, @PathParam("id_logbook") String logbookId) { Response response = checkTenantStrategyHeader(headers); if (response != null) { return response; } final Status status = Status.NOT_IMPLEMENTED; return Response.status(status).entity(getErrorEntity(status)).build(); } /** * Get a list of units * <p> * Note : this is NOT to be handled in item #72. * * @param headers * http header * @return Response NOT_IMPLEMENTED */ @Path("/units") @GET @Produces(MediaType.APPLICATION_JSON) @Consumes(MediaType.APPLICATION_JSON) public Response getUnits(@Context HttpHeaders headers) { Response response = checkTenantStrategyHeader(headers); if (response != null) { return response; } final Status status = Status.NOT_IMPLEMENTED; return Response.status(status).entity(getErrorEntity(status)).build(); } /** * Get a unit * <p> * Note : this is NOT to be handled in item #72. * * @param headers * http header * @param metadataId * the id of the unit metadata * @return Response NOT_IMPLEMENTED */ @Path("/units/{id_md}") @GET @Produces(MediaType.APPLICATION_JSON) @Consumes(MediaType.APPLICATION_JSON) public Response getUnit(@Context HttpHeaders headers, @PathParam("id_md") String metadataId) { Response response = checkTenantStrategyHeader(headers); if (response != null) { return response; } final Status status = Status.NOT_IMPLEMENTED; return Response.status(status).entity(getErrorEntity(status)).build(); } /** * Post a new unit metadata * * @param httpServletRequest * http servlet request to get requester * * @param headers * http header * @param metadataId * the id of the unit metadata * @param createObjectDescription * the workspace description of the unit to be created * @return Response NOT_IMPLEMENTED */ // TODO P1: remove httpServletRequest when requester information sent by // header (X-Requester) @Path("/units/{id_md}") @POST @Produces(MediaType.APPLICATION_JSON) @Consumes(MediaType.APPLICATION_JSON) public Response createUnitMetadata(@Context HttpServletRequest httpServletRequest, @Context HttpHeaders headers, @PathParam("id_md") String metadataId, ObjectDescription createObjectDescription) { if (createObjectDescription == null) { return getUnit(headers, metadataId); } else { // TODO P1: actually no X-Requester header, so send the getRemoteAdr // from HttpServletRequest return createObjectByType(headers, metadataId, createObjectDescription, DataCategory.UNIT, httpServletRequest.getRemoteAddr()); } } /** * Update a unit metadata * <p> * Note : this is NOT to be handled in item #72. * * @param headers * http header * @param metadataId * the id of the unit metadata * @param query * the query as a JsonNode * @return Response NOT_IMPLEMENTED */ @Path("/units/{id_md}") @PUT @Produces(MediaType.APPLICATION_JSON) @Consumes(MediaType.APPLICATION_JSON) public Response updateUnitMetadata(@Context HttpHeaders headers, @PathParam("id_md") String metadataId, JsonNode query) { Response response = checkTenantStrategyHeader(headers); if (response != null) { return response; } final Status status = Status.NOT_IMPLEMENTED; return Response.status(status).entity(getErrorEntity(status)).build(); } /** * Delete a unit metadata * * @param headers * http header * @param metadataId * the id of the unit metadata * @return Response NOT_IMPLEMENTED */ @Path("/units/{id_md}") @DELETE @Produces(MediaType.APPLICATION_JSON) @Consumes(MediaType.APPLICATION_JSON) public Response deleteUnit(@Context HttpHeaders headers, @PathParam("id_md") String metadataId) { Response response = checkTenantStrategyHeader(headers); if (response != null) { return response; } final Status status = Status.NOT_IMPLEMENTED; return Response.status(status).entity(getErrorEntity(status)).build(); } /** * Check the existence of a unit metadata * * @param headers * http header * @param metadataId * the id of the unit metadata * @return Response NOT_IMPLEMENTED */ @Path("/units/{id_md}") @HEAD @Produces(MediaType.APPLICATION_JSON) @Consumes(MediaType.APPLICATION_JSON) public Response checkUnit(@Context HttpHeaders headers, @PathParam("id_md") String metadataId) { Response response = checkTenantStrategyHeader(headers); if (response != null) { return response; } final Status status = Status.NOT_IMPLEMENTED; return Response.status(status).entity(getErrorEntity(status)).build(); } /** * Get a list of Object Groups * <p> * Note : this is NOT to be handled in item #72. * * @param headers * http header * @return Response NOT_IMPLEMENTED */ @Path("/objectgroups") @GET @Produces(MediaType.APPLICATION_JSON) @Consumes(MediaType.APPLICATION_JSON) public Response getObjectGroups(@Context HttpHeaders headers) { Response response = checkTenantStrategyHeader(headers); if (response != null) { return response; } final Status status = Status.NOT_IMPLEMENTED; return Response.status(status).entity(getErrorEntity(status)).build(); } /** * Get a Object Group * <p> * Note : this is NOT to be handled in item #72. * * @param headers * http header * @param metadataId * the id of the Object Group metadata * @return Response NOT_IMPLEMENTED */ @Path("/objectgroups/{id_md}") @GET @Produces({ MediaType.APPLICATION_JSON, MediaType.APPLICATION_OCTET_STREAM, CommonMediaType.ZIP }) @Consumes(MediaType.APPLICATION_JSON) public Response getObjectGroup(@Context HttpHeaders headers, @PathParam("id_md") String metadataId) { Response response = checkTenantStrategyHeader(headers); if (response != null) { return response; } final Status status = Status.NOT_IMPLEMENTED; return Response.status(status).entity(getErrorEntity(status)).build(); } /** * Post a new Object Group metadata * * Note : this is NOT to be handled in item #72. * * @param httpServletRequest * http servlet request to get requester * * @param headers * http header * @param metadataId * the id of the Object Group metadata * @param createObjectDescription * the workspace description of the unit to be created * @return Response Created, not found or internal server error */ // TODO P1: remove httpServletRequest when requester information sent by // header (X-Requester) // TODO P1 : check the existence, in the headers, of the value // X-Http-Method-Override, if set @Path("/objectgroups/{id_md}") @POST @Produces(MediaType.APPLICATION_JSON) @Consumes(MediaType.APPLICATION_JSON) public Response createObjectGroup(@Context HttpServletRequest httpServletRequest, @Context HttpHeaders headers, @PathParam("id_md") String metadataId, ObjectDescription createObjectDescription) { if (createObjectDescription == null) { return getObjectGroup(headers, metadataId); } else { // TODO P1: actually no X-Requester header, so send the getRemoteAdr // from HttpServletRequest return createObjectByType(headers, metadataId, createObjectDescription, DataCategory.OBJECT_GROUP, httpServletRequest.getRemoteAddr()); } } /** * Update a Object Group metadata * <p> * Note : this is NOT to be handled in item #72. * * @param headers * http header * @param metadataId * the id of the unit metadata * @param query * the query as a JsonNode * @return Response NOT_IMPLEMENTED */ @Path("/objectgroups/{id_md}") @PUT @Produces(MediaType.APPLICATION_JSON) @Consumes(MediaType.APPLICATION_JSON) public Response updateObjectGroupMetadata(@Context HttpHeaders headers, @PathParam("id_md") String metadataId, JsonNode query) { Response response = checkTenantStrategyHeader(headers); if (response != null) { return response; } final Status status = Status.NOT_IMPLEMENTED; return Response.status(status).entity(getErrorEntity(status)).build(); } /** * Delete a Object Group metadata * * Note : this is NOT to be handled in item #72. * * @param headers * http header * @param metadataId * the id of the Object Group metadata * @return Response NOT_IMPLEMENTED */ @Path("/objectgroups/{id_md}") @DELETE @Produces(MediaType.APPLICATION_JSON) @Consumes(MediaType.APPLICATION_JSON) public Response deleteObjectGroup(@Context HttpHeaders headers, @PathParam("id_md") String metadataId) { Response response = checkTenantStrategyHeader(headers); if (response != null) { return response; } final Status status = Status.NOT_IMPLEMENTED; return Response.status(status).entity(getErrorEntity(status)).build(); } /** * Check the existence of a Object Group metadata * * Note : this is NOT to be handled in item #72. * * @param headers * http header * @param metadataId * the id of the Object Group metadata * @return Response OK if the object exists, NOT_FOUND otherwise (or * BAD_REQUEST in cas of bad request format) */ @Path("/objectgroups/{id_md}") @HEAD @Produces(MediaType.APPLICATION_JSON) @Consumes(MediaType.APPLICATION_JSON) public Response checkObjectGroup(@Context HttpHeaders headers, @PathParam("id_md") String metadataId) { Response response = checkTenantStrategyHeader(headers); if (response != null) { return response; } final Status status = Status.NOT_IMPLEMENTED; return Response.status(status).entity(getErrorEntity(status)).build(); } private Response buildErrorResponse(VitamCode vitamCode) { return Response.status(vitamCode.getStatus()) .entity(new RequestResponseError().setError(new VitamError(VitamCodeHelper.getCode(vitamCode)) .setContext(vitamCode.getService().getName()).setState(vitamCode.getDomain().getName()) .setMessage(vitamCode.getMessage()).setDescription(vitamCode.getMessage())).toString()) .build(); } private Response badRequestResponse(String message) { return Response.status(Response.Status.BAD_REQUEST).entity("{\"error\":\"" + message + "\"}").build(); } /** * Post a new object * * @param httpServletRequest * http servlet request to get requester * * @param headers * http header * @param reportId * the id of the object * @param createObjectDescription * the object description * @return Response */ // TODO P1: remove httpServletRequest when requester information sent by // header (X-Requester) @Path("/reports/{id_report}") @POST @Produces(MediaType.APPLICATION_JSON) @Consumes(MediaType.APPLICATION_JSON) public Response createReportOrGetInformation(@Context HttpServletRequest httpServletRequest, @Context HttpHeaders headers, @PathParam("id_report") String reportId, ObjectDescription createObjectDescription) { // If the POST is a creation request if (createObjectDescription != null) { // TODO P1: actually no X-Requester header, so send the // getRemoteAddr from HttpServletRequest return createObjectByType(headers, reportId, createObjectDescription, DataCategory.REPORT, httpServletRequest.getRemoteAddr()); } else { return getObjectInformationWithPost(headers, reportId); } } /** * Get a report * * @param headers * http header * @param objectId * the id of the object * @param asyncResponse * @throws IOException * throws an IO Exception */ @Path("/reports/{id_report}") @GET @Produces({ MediaType.APPLICATION_OCTET_STREAM, CommonMediaType.ZIP }) public void getReport(@Context HttpHeaders headers, @PathParam("id_report") String objectId, @Suspended final AsyncResponse asyncResponse) throws IOException { VitamThreadPoolExecutor.getDefaultExecutor().execute(new Runnable() { @Override public void run() { getByCategoryAsync(objectId, headers, DataCategory.REPORT, asyncResponse); } }); } // TODO P1: requester have to come from vitam headers (X-Requester), but // does not exist actually, so use // getRemoteAdr from HttpServletRequest passed as parameter (requester) // Change it when the good header is sent private Response createObjectByType(HttpHeaders headers, String objectId, ObjectDescription createObjectDescription, DataCategory category, String requester) { final Response response = checkTenantStrategyHeader(headers); if (response == null) { VitamCode vitamCode; final String strategyId = HttpHeaderHelper.getHeaderValues(headers, VitamHttpHeader.STRATEGY_ID).get(0); try { final StoredInfoResult result = distribution.storeData(strategyId, objectId, createObjectDescription, category, requester); return Response.status(Status.CREATED).entity(result).build(); } catch (final StorageNotFoundException exc) { LOGGER.error(exc); vitamCode = VitamCode.STORAGE_NOT_FOUND; } catch (final StorageObjectAlreadyExistsException exc) { LOGGER.error(exc); vitamCode = VitamCode.STORAGE_DRIVER_OBJECT_ALREADY_EXISTS; } catch (final StorageException exc) { LOGGER.error(exc); vitamCode = VitamCode.STORAGE_TECHNICAL_INTERNAL_ERROR; } // If here, an error occurred return buildErrorResponse(vitamCode); } return response; } /** * Post a new object manifest * * @param httpServletRequest * http servlet request to get requester * * @param headers * http header * @param manifestId * the id of the object * @param createObjectDescription * the object description * @return Response */ // TODO P1: remove httpServletRequest when requester information sent by // header (X-Requester) @Path("/manifests/{id_manifest}") @POST @Produces(MediaType.APPLICATION_JSON) @Consumes(MediaType.APPLICATION_JSON) public Response createManifestOrGetInformation(@Context HttpServletRequest httpServletRequest, @Context HttpHeaders headers, @PathParam("id_manifest") String manifestId, ObjectDescription createObjectDescription) { // If the POST is a creation request if (createObjectDescription != null) { // TODO P1: actually no X-Requester header, so send the // getRemoteAddr from HttpServletRequest return createObjectByType(headers, manifestId, createObjectDescription, DataCategory.MANIFEST, httpServletRequest.getRemoteAddr()); } else { return getObjectInformationWithPost(headers, manifestId); } } /** * getManifest stored by ingest operation * * @param headers * @param objectId * @param asyncResponse * @throws IOException */ @Path("/manifests/{id_manifest}") @GET @Produces(MediaType.APPLICATION_OCTET_STREAM) public void getManifest(@Context HttpHeaders headers, @PathParam("id_manifest") String objectId, @Suspended final AsyncResponse asyncResponse) throws IOException { VitamThreadPoolExecutor.getDefaultExecutor().execute(new Runnable() { @Override public void run() { getByCategoryAsync(objectId, headers, DataCategory.MANIFEST, asyncResponse); } }); } /** * @param headers * http headers * @return null if strategy and tenant headers have values, a VitamCode * response otherwise */ private VitamCode checkTenantStrategyHeaderAsync(HttpHeaders headers) { if (!HttpHeaderHelper.hasValuesFor(headers, VitamHttpHeader.TENANT_ID) || !HttpHeaderHelper.hasValuesFor(headers, VitamHttpHeader.STRATEGY_ID)) { return VitamCode.STORAGE_MISSING_HEADER; } return null; } /** * Add error response in async response using with vitamCode * * @param vitamCode * vitam error Code * @param asyncResponse * asynchronous response */ private void buildErrorResponseAsync(VitamCode vitamCode, AsyncResponse asyncResponse) { AsyncInputStreamHelper.asyncResponseResume(asyncResponse, Response.status(vitamCode.getStatus()).entity(new RequestResponseError().setError( new VitamError(VitamCodeHelper.getCode(vitamCode)) .setContext(vitamCode.getService().getName()) .setState(vitamCode.getDomain().getName()) .setMessage(vitamCode.getMessage()) .setDescription(vitamCode.getMessage())) .toString()).build()); } private VitamError getErrorEntity(Status status) { return new VitamError(status.name()).setHttpCode(status.getStatusCode()).setContext(STORAGE_MODULE).setState(CODE_VITAM) .setMessage(status.getReasonPhrase()).setDescription(status.getReasonPhrase()); } @Override public void close() { distribution.close(); } }