/** * Copyright French Prime minister Office/SGMAP/DINSIC/Vitam Program (2015-2019) * <p> * contact.vitam@culture.gouv.fr * <p> * This software is a computer program whose purpose is to implement a digital archiving back-office system managing * high volumetry securely and efficiently. * <p> * 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". * <p> * 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. * <p> * 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. * <p> * 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.ihmdemo.appserver; import java.io.File; import java.io.FileInputStream; import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentMap; import java.util.stream.Collectors; import java.util.stream.StreamSupport; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import javax.ws.rs.Consumes; import javax.ws.rs.CookieParam; import javax.ws.rs.DELETE; 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.ProcessingException; import javax.ws.rs.Produces; import javax.ws.rs.QueryParam; 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.Response; import javax.ws.rs.core.Response.Status; import org.apache.shiro.authc.UsernamePasswordToken; import org.apache.shiro.subject.Subject; import org.apache.shiro.util.ThreadContext; import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.node.ArrayNode; import com.google.common.collect.Iterables; import fr.gouv.vitam.access.external.api.AdminCollections; import fr.gouv.vitam.access.external.client.AdminExternalClient; import fr.gouv.vitam.access.external.client.AdminExternalClientFactory; import fr.gouv.vitam.access.external.common.exception.AccessExternalClientException; import fr.gouv.vitam.access.external.common.exception.AccessExternalClientNotFoundException; import fr.gouv.vitam.access.external.common.exception.AccessExternalClientServerException; import fr.gouv.vitam.common.GlobalDataRest; import fr.gouv.vitam.common.ParametersChecker; import fr.gouv.vitam.common.PropertiesUtils; import fr.gouv.vitam.common.client.DefaultAdminClient; import fr.gouv.vitam.common.client.IngestCollection; import fr.gouv.vitam.common.database.builder.request.configuration.BuilderToken; import fr.gouv.vitam.common.database.builder.request.exception.InvalidCreateOperationException; import fr.gouv.vitam.common.exception.BadRequestException; import fr.gouv.vitam.common.exception.InternalServerException; import fr.gouv.vitam.common.exception.InvalidParseOperationException; import fr.gouv.vitam.common.exception.VitamClientException; import fr.gouv.vitam.common.exception.VitamException; import fr.gouv.vitam.common.guid.GUIDFactory; import fr.gouv.vitam.common.i18n.VitamLogbookMessages; import fr.gouv.vitam.common.json.JsonHandler; import fr.gouv.vitam.common.logging.VitamLogger; import fr.gouv.vitam.common.logging.VitamLoggerFactory; import fr.gouv.vitam.common.model.ProcessExecutionStatus; import fr.gouv.vitam.common.model.RequestResponse; import fr.gouv.vitam.common.model.RequestResponseOK; 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.VitamStreamingOutput; import fr.gouv.vitam.common.server.application.resources.ApplicationStatusResource; import fr.gouv.vitam.common.server.application.resources.BasicVitamStatusServiceImpl; import fr.gouv.vitam.common.stream.StreamUtils; import fr.gouv.vitam.common.thread.VitamThreadPoolExecutor; import fr.gouv.vitam.common.thread.VitamThreadUtils; import fr.gouv.vitam.ihmdemo.common.api.IhmDataRest; import fr.gouv.vitam.ihmdemo.common.api.IhmWebAppHeader; import fr.gouv.vitam.ihmdemo.common.pagination.OffsetBasedPagination; import fr.gouv.vitam.ihmdemo.common.pagination.PaginationHelper; import fr.gouv.vitam.ihmdemo.core.DslQueryHelper; import fr.gouv.vitam.ihmdemo.core.JsonTransformer; import fr.gouv.vitam.ihmdemo.core.UiConstants; import fr.gouv.vitam.ihmdemo.core.UserInterfaceTransactionManager; import fr.gouv.vitam.ingest.external.api.exception.IngestExternalException; import fr.gouv.vitam.ingest.external.client.IngestExternalClient; import fr.gouv.vitam.ingest.external.client.IngestExternalClientFactory; import fr.gouv.vitam.logbook.common.exception.LogbookClientException; /** * Web Application Resource class */ @Path("/v1/api") public class WebApplicationResource extends ApplicationStatusResource { private static final VitamLogger LOGGER = VitamLoggerFactory.getInstance(WebApplicationResource.class); private static final String BAD_REQUEST_EXCEPTION_MSG = "Bad request Exception"; private static final String ACCESS_CLIENT_NOT_FOUND_EXCEPTION_MSG = "Access client unavailable"; private static final String ACCESS_SERVER_EXCEPTION_MSG = "Access Server exception"; private static final String INTERNAL_SERVER_ERROR_MSG = "INTERNAL SERVER ERROR"; private static final String SEARCH_CRITERIA_MANDATORY_MSG = "Search criteria payload is mandatory"; private static final String FIELD_ID_KEY = "fieldId"; private static final String NEW_FIELD_VALUE_KEY = "newFieldValue"; private static final String INVALID_ALL_PARENTS_TYPE_ERROR_MSG = "The parameter \"allParents\" is not an array"; private static final String BLANK_OPERATION_ID = "Operation identifier should be filled"; private static final String WORKFLOW_ACTION = "NEXT"; private static final String LOGBOOK_CLIENT_NOT_FOUND_EXCEPTION_MSG = "Logbook Client NOT FOUND Exception"; private static final ConcurrentMap<String, List<Object>> uploadRequestsStatus = new ConcurrentHashMap<>(); private static final int COMPLETE_RESPONSE_SIZE = 3; private static final int GUID_INDEX = 0; private static final int RESPONSE_STATUS_INDEX = 1; private static final int ATR_CONTENT_INDEX = 2; private static final String DEFAULT_CONTEXT = "defaultContext"; private static final String FLOW_TOTAL_CHUNKS_HEADER = "FLOW-TOTAL-CHUNKS"; private static final String FLOW_CHUNK_NUMBER_HEADER = "FLOW-CHUNK-NUMBER"; private final WebApplicationConfig webApplicationConfig; /** * Constructor * * @param webApplicationConfig */ public WebApplicationResource(WebApplicationConfig webApplicationConfig) { super(new BasicVitamStatusServiceImpl(), webApplicationConfig.getTenants()); this.webApplicationConfig = webApplicationConfig; } /** * Retrieve all the messages for logbook * * @return Response */ @GET @Path("/messages/logbook") @Produces(MediaType.APPLICATION_JSON) public Response getLogbookMessages() { // TODO P0 : If translation key could be the same in different // .properties file, MUST add an unique prefix per // file return Response.status(Status.OK).entity(VitamLogbookMessages.getAllMessages()).build(); } /** * @param headers header needed for the request: X-TENANT-ID (mandatory), X-LIMIT/X-OFFSET (not mandatory) * @param sessionId json session id from shiro * @param criteria criteria search for units * @return Reponse */ @POST @Path("/archivesearch/units") @Produces(MediaType.APPLICATION_JSON) public Response getArchiveSearchResult(@Context HttpHeaders headers, @CookieParam("JSESSIONID") String sessionId, String criteria) { ParametersChecker.checkParameter(SEARCH_CRITERIA_MANDATORY_MSG, criteria); String requestId; RequestResponse result; OffsetBasedPagination pagination = null; try { pagination = new OffsetBasedPagination(headers); } catch (final VitamException e) { LOGGER.error("Bad request Exception ", e); return Response.status(Status.BAD_REQUEST).build(); } final List<String> requestIds = HttpHeaderHelper.getHeaderValues(headers, IhmWebAppHeader.REQUEST_ID.name()); Integer tenantId = getTenantId(headers); if (requestIds != null) { requestId = requestIds.get(0); // get result from shiro session try { result = RequestResponseOK.getFromJsonNode(PaginationHelper.getResult(sessionId, pagination)); return Response.status(Status.OK).entity(result).header(GlobalDataRest.X_REQUEST_ID, requestId) .header(IhmDataRest.X_OFFSET, pagination.getOffset()) .header(IhmDataRest.X_LIMIT, pagination.getLimit()).build(); } catch (final VitamException e) { LOGGER.error("Bad request Exception ", e); return Response.status(Status.BAD_REQUEST).header(GlobalDataRest.X_REQUEST_ID, requestId).build(); } } else { requestId = GUIDFactory.newRequestIdGUID(tenantId).toString(); try { SanityChecker.checkJsonAll(JsonHandler.toJsonNode(criteria)); final Map<String, String> criteriaMap = JsonHandler.getMapStringFromString(criteria); final JsonNode preparedQueryDsl = DslQueryHelper.createSelectElasticsearchDSLQuery(criteriaMap); result = UserInterfaceTransactionManager.searchUnits(preparedQueryDsl, getTenantId(headers)); // save result PaginationHelper.setResult(sessionId, result.toJsonNode()); // pagination result = RequestResponseOK.getFromJsonNode(PaginationHelper.getResult(result.toJsonNode(), pagination)); return Response.status(Status.OK).entity(result).build(); } catch (final InvalidCreateOperationException | InvalidParseOperationException e) { LOGGER.error(BAD_REQUEST_EXCEPTION_MSG, e); return Response.status(Status.BAD_REQUEST).build(); } catch (final AccessExternalClientServerException e) { LOGGER.error(ACCESS_SERVER_EXCEPTION_MSG, e); return Response.status(Status.INTERNAL_SERVER_ERROR).build(); } catch (final AccessExternalClientNotFoundException e) { LOGGER.error(ACCESS_CLIENT_NOT_FOUND_EXCEPTION_MSG, e); return Response.status(Status.NOT_FOUND).build(); } catch (final Exception e) { LOGGER.error(INTERNAL_SERVER_ERROR_MSG, e); return Response.status(Status.INTERNAL_SERVER_ERROR).build(); } } } /** * @param headers * @param unitId archive unit id * @return archive unit details */ @GET @Path("/archivesearch/unit/{id}") @Produces(MediaType.APPLICATION_JSON) public Response getArchiveUnitDetails(@Context HttpHeaders headers, @PathParam("id") String unitId) { ParametersChecker.checkParameter(SEARCH_CRITERIA_MANDATORY_MSG, unitId); try { SanityChecker.checkJsonAll(JsonHandler.toJsonNode(unitId)); // Prepare required map final Map<String, String> selectUnitIdMap = new HashMap<>(); selectUnitIdMap.put(UiConstants.SELECT_BY_ID.toString(), unitId); selectUnitIdMap.put(DslQueryHelper.PROJECTION_DSL, BuilderToken.GLOBAL.RULES.exactToken()); final JsonNode preparedQueryDsl = DslQueryHelper.createSelectDSLQuery(selectUnitIdMap); final RequestResponse archiveDetails = UserInterfaceTransactionManager .getArchiveUnitDetails(preparedQueryDsl, unitId, getTenantId(headers)); return Response.status(Status.OK).entity(archiveDetails).build(); } catch (final InvalidCreateOperationException | InvalidParseOperationException e) { LOGGER.error(BAD_REQUEST_EXCEPTION_MSG, e); return Response.status(Status.BAD_REQUEST).build(); } catch (final AccessExternalClientServerException e) { LOGGER.error(ACCESS_SERVER_EXCEPTION_MSG, e); return Response.status(Status.INTERNAL_SERVER_ERROR).build(); } catch (final AccessExternalClientNotFoundException e) { LOGGER.error(ACCESS_CLIENT_NOT_FOUND_EXCEPTION_MSG, e); return Response.status(Status.NOT_FOUND).build(); } catch (final Exception e) { LOGGER.error(INTERNAL_SERVER_ERROR_MSG, e); return Response.status(Status.INTERNAL_SERVER_ERROR).build(); } } /** * @param headers header needed for the request: X-TENANT-ID (mandatory), X-LIMIT/X-OFFSET (not mandatory) * @param sessionId json session id from shiro * @param options the queries for searching * @return Response */ @POST @Path("/logbook/operations") @Produces(MediaType.APPLICATION_JSON) public Response getLogbookResult(@Context HttpHeaders headers, @CookieParam("JSESSIONID") String sessionId, String options) { ParametersChecker.checkParameter("cookie is mandatory", sessionId); String requestId = null; RequestResponse result = null; OffsetBasedPagination pagination = null; try { pagination = new OffsetBasedPagination(headers); } catch (final VitamException e) { LOGGER.error("Bad request Exception ", e); return Response.status(Status.BAD_REQUEST).build(); } final List<String> requestIds = HttpHeaderHelper.getHeaderValues(headers, IhmWebAppHeader.REQUEST_ID.name()); Integer tenantId = getTenantId(headers); if (requestIds != null) { requestId = requestIds.get(0); // get result from shiro session try { result = RequestResponseOK.getFromJsonNode(PaginationHelper.getResult(sessionId, pagination)); return Response.status(Status.OK).entity(result).header(GlobalDataRest.X_REQUEST_ID, requestId) .header(IhmDataRest.X_OFFSET, pagination.getOffset()) .header(IhmDataRest.X_LIMIT, pagination.getLimit()).build(); } catch (final VitamException e) { LOGGER.error("Bad request Exception ", e); return Response.status(Status.BAD_REQUEST).header(GlobalDataRest.X_REQUEST_ID, requestId).build(); } } else { requestId = GUIDFactory.newRequestIdGUID(tenantId).toString(); try { ParametersChecker.checkParameter(SEARCH_CRITERIA_MANDATORY_MSG, options); SanityChecker.checkJsonAll(JsonHandler.toJsonNode(options)); final Map<String, String> optionsMap = JsonHandler.getMapStringFromString(options); final JsonNode query = DslQueryHelper.createSingleQueryDSL(optionsMap); result = UserInterfaceTransactionManager.selectOperation(query, tenantId); // save result PaginationHelper.setResult(sessionId, result.toJsonNode()); // pagination result = RequestResponseOK.getFromJsonNode(PaginationHelper.getResult(result.toJsonNode(), pagination)); } catch (final InvalidCreateOperationException | InvalidParseOperationException e) { LOGGER.error("Bad request Exception ", e); return Response.status(Status.BAD_REQUEST).header(GlobalDataRest.X_REQUEST_ID, requestId).build(); } catch (final LogbookClientException e) { LOGGER.error("Logbook Client NOT FOUND Exception ", e); return Response.status(Status.NOT_FOUND).header(GlobalDataRest.X_REQUEST_ID, requestId).build(); } catch (final Exception e) { LOGGER.error(INTERNAL_SERVER_ERROR_MSG, e); return Response.status(Status.INTERNAL_SERVER_ERROR).header(GlobalDataRest.X_REQUEST_ID, requestId) .build(); } return Response.status(Status.OK).entity(result).header(GlobalDataRest.X_REQUEST_ID, requestId) .header(IhmDataRest.X_OFFSET, pagination.getOffset()) .header(IhmDataRest.X_LIMIT, pagination.getLimit()).build(); } } /** * @param headers * @param operationId id of operation * @param options the queries for searching * @return Response */ @POST @Path("/logbook/operations/{idOperation}") @Produces(MediaType.APPLICATION_JSON) public Response getLogbookResultById(@Context HttpHeaders headers, @PathParam("idOperation") String operationId, String options) { RequestResponse result = null; try { ParametersChecker.checkParameter(SEARCH_CRITERIA_MANDATORY_MSG, options); SanityChecker.checkJsonAll(JsonHandler.toJsonNode(options)); result = UserInterfaceTransactionManager.selectOperationbyId(operationId, getTenantId(headers)); } catch (final IllegalArgumentException | InvalidParseOperationException e) { LOGGER.error(e); return Response.status(Status.BAD_REQUEST).entity(e.getMessage()).build(); } catch (final LogbookClientException e) { LOGGER.error("Logbook Client NOT FOUND Exception ", e); return Response.status(Status.NOT_FOUND).build(); } catch (final Exception e) { LOGGER.error("INTERNAL SERVER ERROR", e); return Response.status(Status.INTERNAL_SERVER_ERROR).build(); } return Response.status(Status.OK).entity(result).build(); } /** * upload : API Endpoint that can Handle chunk mode. Chunks information are given in header (Fast catch of these * header are present in the code) <br /> * The front should give some information * <ul> * <li>Flow-Chunk-Number => The index of the current chunk</li> * <li>Flow-Chunk-Size => The configured maximal size of a chunk</li> * <li>Flow-Current-Chunk-Size => The size of the current chunk</li> * <li>Flow-Total-Size => The total size of the file (All chunks)</li> * <li>Flow-Identifier => The identifier of the flow</li> * <li>Flow-Filename => The file name</li> * <li>Flow-Relative-Path => (?)The relative path (or the file name only)</li> * <li>Flow-Total-Chunks => The number of chunks</li> * </ul> * * @param request * @param response * @param stream data input stream for the current chunk * @param headers HTTP Headers containing chunk information * @return Response */ @Path("ingest/upload") @POST @Consumes(MediaType.APPLICATION_OCTET_STREAM) @Produces(MediaType.APPLICATION_JSON) public Response upload(@Context HttpServletRequest request, @Context HttpServletResponse response, @Context HttpHeaders headers, InputStream stream) { String operationGuidFirstLevel = null; File temporarySipFile = null; try { // Received chunk index final int currentChunkIndex = Integer.parseInt(headers.getHeaderString(FLOW_CHUNK_NUMBER_HEADER)); // Total number of chunks final int totalChunks = Integer.parseInt(headers.getHeaderString(FLOW_TOTAL_CHUNKS_HEADER)); String contextId = headers.getHeaderString(GlobalDataRest.X_CONTEXT_ID); String action = headers.getHeaderString(GlobalDataRest.X_ACTION); if (currentChunkIndex == 1) { // GUID operation (Server Application level) operationGuidFirstLevel = GUIDFactory.newGUID().getId(); temporarySipFile = PropertiesUtils.fileFromTmpFolder(operationGuidFirstLevel); // Write the first chunk try (FileOutputStream outputStream = new FileOutputStream(temporarySipFile)) { StreamUtils.copy(stream, outputStream); } } else { operationGuidFirstLevel = headers.getHeaderString(GlobalDataRest.X_REQUEST_ID); temporarySipFile = PropertiesUtils.fileFromTmpFolder(operationGuidFirstLevel); // Append the received chunk to the temporary file try (final FileOutputStream outputStream = new FileOutputStream(temporarySipFile, true)) { StreamUtils.copy(stream, outputStream); } } // if it is the last chunk => start INGEST upload if (currentChunkIndex == totalChunks) { startUpload(operationGuidFirstLevel, getTenantId(headers), contextId, action); } } catch (final IOException e) { LOGGER.error("Upload failed", e); return Response.status(Status.INTERNAL_SERVER_ERROR).build(); } catch (final RuntimeException e) { LOGGER.error("Upload failed", e); return Response.status(Status.INTERNAL_SERVER_ERROR).build(); } try { return Response .status(Status.OK).entity(JsonHandler.getFromString( "{\"" + GlobalDataRest.X_REQUEST_ID.toLowerCase() + "\":\"" + operationGuidFirstLevel + "\"}")) .header(GlobalDataRest.X_REQUEST_ID, operationGuidFirstLevel) .build(); } catch (InvalidParseOperationException e) { LOGGER.error("Upload failed", e); return Response.status(Status.INTERNAL_SERVER_ERROR) .header(GlobalDataRest.X_REQUEST_ID, operationGuidFirstLevel). build(); } } private void startUpload(String operationGUID, Integer tenantId, String contextId, String action) { final IngestThread ingestThread = new IngestThread(operationGUID, tenantId, contextId, action); ingestThread.start(); } class IngestThread extends Thread { String operationGuidFirstLevel; Integer tenantId; String contextId; String action; IngestThread(String operationGuidFirstLevel, Integer tenantId, String contextId, String action) { this.operationGuidFirstLevel = operationGuidFirstLevel; this.tenantId = tenantId; this.contextId = contextId; this.action = action; } @Override public void run() { // start the upload Response finalResponse = null; File file = null; final File temporarSipFile = PropertiesUtils.fileFromTmpFolder(operationGuidFirstLevel); try (IngestExternalClient client = IngestExternalClientFactory.getInstance().getClient()) { try { finalResponse = client.upload(new FileInputStream(temporarSipFile), tenantId, contextId, action); final String guid = finalResponse.getHeaderString(GlobalDataRest.X_REQUEST_ID); final List<Object> finalResponseDetails = new ArrayList<>(); finalResponseDetails.add(guid); finalResponseDetails.add(Status.fromStatusCode(finalResponse.getStatus())); uploadRequestsStatus.put(operationGuidFirstLevel, finalResponseDetails); } finally { DefaultAdminClient.staticConsumeAnyEntityAndClose(finalResponse); } } catch (IOException | VitamException e) { LOGGER.error("Upload failed", e); final List<Object> finalResponseDetails = new ArrayList<>(); finalResponseDetails.add(operationGuidFirstLevel); finalResponseDetails.add(Status.INTERNAL_SERVER_ERROR); uploadRequestsStatus.put(operationGuidFirstLevel, finalResponseDetails); if (file != null) { file.delete(); } } finally { if (temporarSipFile != null) { temporarSipFile.delete(); } } } } /** * Check if the upload operation is done * * @param operationId * @return the Response */ @Path("check/{id_op}") @GET @Produces(MediaType.APPLICATION_OCTET_STREAM) public Response checkUploadOperation(@PathParam("id_op") String operationId, @Context HttpHeaders headers, @QueryParam("action") String action) throws VitamClientException, IngestExternalException { // TODO Need a tenantId test for checking upload (Only IHM-DEMO scope, // dont call VITAM backend) ? // 1- Check if the requested operation is done ParametersChecker.checkParameter(BLANK_OPERATION_ID, operationId); //mapping X-request-ID final List<Object> responseDetails = uploadRequestsStatus.get(operationId); Integer tenantId = getTenantId(headers); if (responseDetails != null) { try (IngestExternalClient client = IngestExternalClientFactory.getInstance().getClient()) { String id = responseDetails.get(GUID_INDEX).toString(); Response response = client.getOperationStatus(id, tenantId); if (Status.fromStatusCode(response.getStatus()).equals(Status.ACCEPTED)) { return Response.status(Status.NO_CONTENT).header(GlobalDataRest.X_REQUEST_ID, operationId).build(); } if (Status.fromStatusCode(response.getStatus()).equals(Status.NOT_FOUND)) { return Response.status(Status.NO_CONTENT).header(GlobalDataRest.X_REQUEST_ID, operationId).build(); } //STEP BY STEP if (Status.fromStatusCode(response.getStatus()).equals(Status.OK) && action.equals(WORKFLOW_ACTION)) { JsonNode lastEvent = getLogBookOperationStatus(id, tenantId); // ingestExternalClient client int status = getStatus(lastEvent); return Response.status(status).header(GlobalDataRest.X_REQUEST_ID, operationId).build(); } if (Status.fromStatusCode(response.getStatus()).equals(Status.OK)) { try (IngestExternalClient ingestExternalClient = IngestExternalClientFactory.getInstance() .getClient()) { response = ingestExternalClient .downloadObjectAsync(id, IngestCollection.REPORTS, tenantId); InputStream inputStream = response.readEntity(InputStream.class); JsonNode lastEvent = getLogBookOperationStatus(id, tenantId); // ingestExternalClient client int status = getStatus(lastEvent); if (inputStream != null) { return Response.status(status).entity(inputStream) .type(MediaType.APPLICATION_OCTET_STREAM_TYPE) .header("Content-Disposition", "attachment; filename=ATR_" + id + ".xml") .header(GlobalDataRest.X_REQUEST_ID, operationId).build(); } } } else { return Response.status(response.getStatus()).header(GlobalDataRest.X_REQUEST_ID, operationId) .build(); } } catch (Exception e) { LOGGER.error(e); return Response.status(Status.INTERNAL_SERVER_ERROR).header(GlobalDataRest.X_REQUEST_ID, operationId) .entity(e).build(); } } // 2- Return the created GUID return Response.status(Status.NO_CONTENT).header(GlobalDataRest.X_REQUEST_ID, operationId) .build(); } private static JsonNode getLogBookOperationStatus(String operationId, Integer tenantId) throws LogbookClientException, InvalidParseOperationException { final RequestResponse<JsonNode> result = UserInterfaceTransactionManager.selectOperationbyId(operationId, tenantId); RequestResponseOK<JsonNode> responseOK = (RequestResponseOK<JsonNode>) result; List<JsonNode> results = responseOK.getResults(); JsonNode operation = results.get(0); ArrayNode events = (ArrayNode) operation.get("events"); return Iterables.getLast(events); } private static int getStatus(JsonNode lastEvent) throws Exception { String out = String.valueOf(lastEvent.get("outcome").asText()); if (out == null) { throw new Exception("parsing Error"); } switch (out) { case "WARNING": return 206; case "OK": return 200; case "KO": return 400; } return 500; } /** * Once done, clear the Upload operation history * * @param operationId * @return the Response */ @Path("clear/{id_op}") @GET public Response clearUploadOperationHistory(@PathParam("id_op") String operationId) { // TODO Need a tenantId test for checking upload (Only IHM-DEMO scope, // dont call VITAM backend) ? final List<Object> responseDetails = uploadRequestsStatus.get(operationId); if (responseDetails != null) { // Clean up uploadRequestsStatus uploadRequestsStatus.remove(operationId); if (responseDetails.size() == COMPLETE_RESPONSE_SIZE) { final File file = (File) responseDetails.get(ATR_CONTENT_INDEX); file.delete(); } // Cleaning process succeeded return Response.status(Status.OK).header(GlobalDataRest.X_REQUEST_ID, operationId).build(); } else { // Cleaning process failed return Response.status(Status.BAD_REQUEST).header(GlobalDataRest.X_REQUEST_ID, operationId).build(); } } /** * Update Archive Units * * @param headers HTTP Headers * @param updateSet contains updated field * @param unitId archive unit id * @return archive unit details */ @POST @Path("/archiveupdate/units/{id}") @Produces(MediaType.APPLICATION_JSON) @Consumes(MediaType.APPLICATION_JSON) public Response updateArchiveUnitDetails(@Context HttpHeaders headers, @PathParam("id") String unitId, String updateSet) { try { ParametersChecker.checkParameter(SEARCH_CRITERIA_MANDATORY_MSG, unitId); SanityChecker.checkJsonAll(JsonHandler.toJsonNode(unitId)); SanityChecker.checkJsonAll(JsonHandler.toJsonNode(updateSet)); } catch (final IllegalArgumentException | InvalidParseOperationException e) { LOGGER.error(e); return Response.status(Status.BAD_REQUEST).entity(e.getMessage()).build(); } try { // Parse updateSet final Map<String, String> updateUnitIdMap = new HashMap<>(); final JsonNode modifiedFields = JsonHandler.getFromString(updateSet); if (modifiedFields != null && modifiedFields.isArray()) { for (final JsonNode modifiedField : modifiedFields) { updateUnitIdMap.put(modifiedField.get(FIELD_ID_KEY).textValue(), modifiedField.get(NEW_FIELD_VALUE_KEY).textValue()); } } // Add ID to set root part updateUnitIdMap.put(UiConstants.SELECT_BY_ID.toString(), unitId); final JsonNode preparedQueryDsl = DslQueryHelper.createUpdateDSLQuery(updateUnitIdMap); final RequestResponse archiveDetails = UserInterfaceTransactionManager.updateUnits(preparedQueryDsl, unitId, getTenantId(headers)); return Response.status(Status.OK).entity(archiveDetails).build(); } catch (final InvalidCreateOperationException | InvalidParseOperationException e) { LOGGER.error(BAD_REQUEST_EXCEPTION_MSG, e); return Response.status(Status.BAD_REQUEST).build(); } catch (final AccessExternalClientServerException e) { LOGGER.error(ACCESS_SERVER_EXCEPTION_MSG, e); return Response.status(Status.INTERNAL_SERVER_ERROR).build(); } catch (final AccessExternalClientNotFoundException e) { LOGGER.error(ACCESS_CLIENT_NOT_FOUND_EXCEPTION_MSG, e); return Response.status(Status.NOT_FOUND).build(); } catch (final Exception e) { LOGGER.error(INTERNAL_SERVER_ERROR_MSG, e); return Response.status(Status.INTERNAL_SERVER_ERROR).build(); } } /** * @param headers HTTP header needed for the request: X-TENANT-ID (mandatory), X-LIMIT/X-OFFSET (not mandatory) * @param sessionId json session id from shiro * @param options the queries for searching * @return Response */ @POST @Path("/admin/formats") @Produces(MediaType.APPLICATION_JSON) public Response getFileFormats(@Context HttpHeaders headers, @CookieParam("JSESSIONID") String sessionId, String options) { // FIXME P0: Pagination rollbacked because of error on mongo/ES indexation // FIXME Pagination should be use as in others endpoints after solution found (See Item #2227) ParametersChecker .checkParameter(SEARCH_CRITERIA_MANDATORY_MSG, options); try (final AdminExternalClient adminClient = AdminExternalClientFactory .getInstance().getClient()) { Integer tenantId = getTenantId(headers); SanityChecker.checkJsonAll(JsonHandler.toJsonNode(options)); final Map<String, String> optionsMap = JsonHandler.getMapStringFromString(options); final JsonNode query = DslQueryHelper.createSingleQueryDSL(optionsMap); final RequestResponse result = adminClient.findDocuments( AdminCollections.FORMATS, query, tenantId); return Response.status(Status.OK).entity(result).build(); } catch (final InvalidCreateOperationException | InvalidParseOperationException e) { LOGGER.error("Bad request Exception ", e); return Response.status(Status.BAD_REQUEST).build(); } catch (final AccessExternalClientNotFoundException e) { LOGGER.error("AdminManagementClient NOT FOUND Exception ", e); return Response.status(Status.NOT_FOUND).build(); } catch (final Exception e) { LOGGER.error(INTERNAL_SERVER_ERROR_MSG, e); return Response.status(Status.INTERNAL_SERVER_ERROR).build(); } } /** * @param headers HTTP Headers * @param formatId id of format * @param options the queries for searching * @return Response */ @POST @Path("/admin/formats/{idFormat}") @Produces(MediaType.APPLICATION_JSON) public Response getFormatById(@Context HttpHeaders headers, @PathParam("idFormat") String formatId, String options) { RequestResponse result = null; try (final AdminExternalClient adminClient = AdminExternalClientFactory.getInstance().getClient()) { ParametersChecker.checkParameter(SEARCH_CRITERIA_MANDATORY_MSG, options); SanityChecker.checkJsonAll(JsonHandler.toJsonNode(options)); ParametersChecker.checkParameter("Format Id is mandatory", formatId); SanityChecker.checkJsonAll(JsonHandler.toJsonNode(formatId)); result = adminClient.findDocumentById(AdminCollections.FORMATS, formatId, getTenantId(headers)); return Response.status(Status.OK).entity(result).build(); } catch (final InvalidParseOperationException e) { LOGGER.error(BAD_REQUEST_EXCEPTION_MSG, e); return Response.status(Status.BAD_REQUEST).build(); } catch (final AccessExternalClientNotFoundException e) { LOGGER.error("AdminManagementClient NOT FOUND Exception ", e); return Response.status(Status.NOT_FOUND).build(); } catch (final Exception e) { LOGGER.error(INTERNAL_SERVER_ERROR_MSG, e); return Response.status(Status.INTERNAL_SERVER_ERROR).build(); } } /** * check the referential format * * @param headers HTTP Headers * @param input the format file xml * @return If the formet is valid, return ok. If not, return the list of errors */ @POST @Path("/format/check") @Consumes(MediaType.APPLICATION_OCTET_STREAM) @Produces(MediaType.APPLICATION_JSON) public Response checkRefFormat(@Context HttpHeaders headers, InputStream input) { Response response = null; try (final AdminExternalClient adminClient = AdminExternalClientFactory.getInstance().getClient()) { try { response = adminClient.checkDocuments(AdminCollections.FORMATS, input, getTenantId(headers)); switch (response.getStatusInfo().getFamily()) { case SUCCESSFUL: return Response.status(Status.OK).build(); default: return Response.status(Status.INTERNAL_SERVER_ERROR).build(); } } catch (final AccessExternalClientNotFoundException e) { LOGGER.error("AdminManagementClient NOT FOUND Exception ", e); return Response.status(Status.NOT_FOUND).build(); } catch (final Exception e) { LOGGER.error(e); return Response.status(Status.INTERNAL_SERVER_ERROR).build(); } finally { //close response before input StreamUtils.closeSilently(input); adminClient.consumeAnyEntityAndClose(response); } } } /** * Upload the referential format in the base * * @param headers HTTP Headers * @param input the format file xml * @return Response */ @POST @Path("/format/upload") @Consumes(MediaType.APPLICATION_OCTET_STREAM) @Produces(MediaType.APPLICATION_JSON) public Response uploadRefFormat(@Context HttpHeaders headers, InputStream input) { Response response = null; try (final AdminExternalClient adminClient = AdminExternalClientFactory.getInstance().getClient()) { response = adminClient.createDocuments(AdminCollections.FORMATS, input, getTenantId(headers)); switch (response.getStatusInfo().getFamily()) { case SUCCESSFUL: return Response.status(Status.OK).build(); default: return Response.status(Status.INTERNAL_SERVER_ERROR).build(); } } catch (final AccessExternalClientException e) { LOGGER.error("AdminManagementClient NOT FOUND Exception ", e); return Response.status(Status.FORBIDDEN).build(); } catch (final Exception e) { return Response.status(Status.INTERNAL_SERVER_ERROR).build(); } finally { StreamUtils.closeSilently(input); DefaultAdminClient.staticConsumeAnyEntityAndClose(response); } } /** * Retrieve an ObjectGroup as Json data based on the provided ObjectGroup id * * @param headers HTTP Headers * @param objectGroupId the object group Id * @return a response containing a json with informations about usages and versions for an object group */ @GET @Path("/archiveunit/objects/{idOG}") @Produces(MediaType.APPLICATION_JSON) public Response getArchiveObjectGroup(@Context HttpHeaders headers, @PathParam("idOG") String objectGroupId) { ParametersChecker.checkParameter(SEARCH_CRITERIA_MANDATORY_MSG, objectGroupId); try { SanityChecker.checkJsonAll(JsonHandler.toJsonNode(objectGroupId)); final HashMap<String, String> qualifierProjection = new HashMap<>(); qualifierProjection.put("projection_qualifiers", "#qualifiers"); final JsonNode preparedQueryDsl = DslQueryHelper.createSelectDSLQuery(qualifierProjection); final RequestResponse searchResult = UserInterfaceTransactionManager.selectObjectbyId(preparedQueryDsl, objectGroupId, getTenantId(headers)); return Response.status(Status.OK).entity(JsonTransformer.transformResultObjects(searchResult.toJsonNode())) .build(); } catch (final InvalidCreateOperationException | InvalidParseOperationException e) { LOGGER.error(BAD_REQUEST_EXCEPTION_MSG, e); return Response.status(Status.BAD_REQUEST).build(); } catch (final AccessExternalClientServerException e) { LOGGER.error(ACCESS_SERVER_EXCEPTION_MSG, e); return Response.status(Status.INTERNAL_SERVER_ERROR).build(); } catch (final AccessExternalClientNotFoundException e) { LOGGER.error(ACCESS_CLIENT_NOT_FOUND_EXCEPTION_MSG, e); return Response.status(Status.NOT_FOUND).build(); } catch (final Exception e) { LOGGER.error(INTERNAL_SERVER_ERROR_MSG, e); return Response.status(Status.INTERNAL_SERVER_ERROR).build(); } } /** * Retrieve an Object data as an input stream * * @param headers HTTP Headers * @param objectGroupId the object group Id * @param usage additional mandatory parameters usage * @param version additional mandatory parameters version * @param filename additional mandatory parameters filename * @param tenantId the tenant id * @param asyncResponse will return the inputstream */ @GET @Path("/archiveunit/objects/download/{idOG}") @Produces(MediaType.APPLICATION_OCTET_STREAM) public void getObjectAsInputStreamAsync(@Context HttpHeaders headers, @PathParam("idOG") String objectGroupId, @QueryParam("usage") String usage, @QueryParam("version") String version, @QueryParam("filename") String filename, @QueryParam("tenantId") Integer tenantId, @Suspended final AsyncResponse asyncResponse) { VitamThreadUtils.getVitamSession().setRequestId(GUIDFactory.newRequestIdGUID(tenantId)); VitamThreadPoolExecutor.getDefaultExecutor() .execute(() -> asyncGetObjectStream(asyncResponse, objectGroupId, usage, version, filename, tenantId)); } /** * Retrieve an Object data stored by ingest operation as an input stream * * @param headers HTTP Headers * @param objectId * @param type * @param asyncResponse */ @GET @Path("/ingests/{idObject}/{type}") @Produces(MediaType.APPLICATION_OCTET_STREAM) public void getObjectFromStorageAsInputStreamAsync(@Context HttpHeaders headers, @PathParam("idObject") String objectId, @PathParam("type") String type, @Suspended final AsyncResponse asyncResponse) { Integer tenantId = getTenantId(headers); VitamThreadUtils.getVitamSession().setRequestId(GUIDFactory.newRequestIdGUID(tenantId)); VitamThreadPoolExecutor.getDefaultExecutor() .execute(() -> asyncGetObjectStorageStream(asyncResponse, objectId, type, tenantId)); } private void asyncGetObjectStorageStream(AsyncResponse asyncResponse, String objectId, String type, Integer tenantId) { try (IngestExternalClient client = IngestExternalClientFactory.getInstance().getClient()) { IngestCollection collection = IngestCollection.valueOf(type.toUpperCase()); Response response = client.downloadObjectAsync(objectId, collection, tenantId); final AsyncInputStreamHelper helper = new AsyncInputStreamHelper(asyncResponse, response); if (response.getStatus() == Status.OK.getStatusCode()) { helper.writeResponse(Response.ok().header("Content-Disposition", "filename=" + objectId + ".xml")); } else { helper.writeResponse(Response.status(response.getStatus())); } } catch (IllegalArgumentException exc) { LOGGER.error(BAD_REQUEST_EXCEPTION_MSG, exc); AsyncInputStreamHelper.asyncResponseResume(asyncResponse, Response.status(Status.BAD_REQUEST).build()); } catch (final IngestExternalException exc) { LOGGER.error(INTERNAL_SERVER_ERROR_MSG, exc); AsyncInputStreamHelper.asyncResponseResume(asyncResponse, Response.status(Status.INTERNAL_SERVER_ERROR).build()); } } private void asyncGetObjectStream(AsyncResponse asyncResponse, String objectGroupId, String usage, String version, String filename, Integer tenantId) { try { SanityChecker.checkJsonAll(JsonHandler.toJsonNode(objectGroupId)); SanityChecker.checkJsonAll(JsonHandler.toJsonNode(version)); SanityChecker.checkJsonAll(JsonHandler.toJsonNode(usage)); SanityChecker.checkJsonAll(JsonHandler.toJsonNode(filename)); ParametersChecker.checkParameter(SEARCH_CRITERIA_MANDATORY_MSG, objectGroupId); ParametersChecker.checkParameter(SEARCH_CRITERIA_MANDATORY_MSG, usage); ParametersChecker.checkParameter(SEARCH_CRITERIA_MANDATORY_MSG, version); ParametersChecker.checkParameter(SEARCH_CRITERIA_MANDATORY_MSG, filename); } catch (final InvalidParseOperationException exc) { AsyncInputStreamHelper.asyncResponseResume(asyncResponse, Response.status(Status.BAD_REQUEST).build()); return; } catch (final IllegalArgumentException exc) { AsyncInputStreamHelper.asyncResponseResume(asyncResponse, Response.status(Status.PRECONDITION_FAILED).build()); return; } try { final HashMap<String, String> emptyMap = new HashMap<>(); final JsonNode preparedQueryDsl = DslQueryHelper.createSelectDSLQuery(emptyMap); UserInterfaceTransactionManager.getObjectAsInputStream(asyncResponse, preparedQueryDsl, objectGroupId, usage, Integer.parseInt(version), filename, tenantId); } catch (InvalidParseOperationException | InvalidCreateOperationException exc) { LOGGER.error(BAD_REQUEST_EXCEPTION_MSG, exc); AsyncInputStreamHelper.asyncResponseResume(asyncResponse, Response.status(Status.BAD_REQUEST).build()); } catch (final AccessExternalClientNotFoundException exc) { LOGGER.error(ACCESS_CLIENT_NOT_FOUND_EXCEPTION_MSG, exc); AsyncInputStreamHelper.asyncResponseResume(asyncResponse, Response.status(Status.NOT_FOUND).build()); } catch (final AccessExternalClientServerException exc) { LOGGER.error(ACCESS_SERVER_EXCEPTION_MSG, exc); AsyncInputStreamHelper.asyncResponseResume(asyncResponse, Response.status(Status.INTERNAL_SERVER_ERROR).build()); } catch (final Exception exc) { LOGGER.error(INTERNAL_SERVER_ERROR_MSG, exc); AsyncInputStreamHelper.asyncResponseResume(asyncResponse, Response.status(Status.INTERNAL_SERVER_ERROR).build()); } } /***** rules Management ************/ /** * @param headers HTTP header needed for the request: X-TENANT-ID (mandatory), X-LIMIT/X-OFFSET (not mandatory) * @param sessionId json session id from shiro * @param options the queries for searching * @return Response */ @POST @Path("/admin/rules") @Produces(MediaType.APPLICATION_JSON) public Response getFileRules(@Context HttpHeaders headers, @CookieParam("JSESSIONID") String sessionId, String options) { ParametersChecker.checkParameter("cookie is mandatory", sessionId); String requestId = null; RequestResponse result = null; OffsetBasedPagination pagination = null; try { pagination = new OffsetBasedPagination(headers); } catch (final VitamException e) { LOGGER.error("Bad request Exception ", e); return Response.status(Status.BAD_REQUEST).build(); } final List<String> requestIds = HttpHeaderHelper.getHeaderValues(headers, IhmWebAppHeader.REQUEST_ID.name()); Integer tenantId = getTenantId(headers); if (requestIds != null) { requestId = requestIds.get(0); // get result from shiro session try { result = RequestResponseOK.getFromJsonNode(PaginationHelper.getResult(sessionId, pagination)); // save result PaginationHelper.setResult(sessionId, result.toJsonNode()); // pagination result = RequestResponseOK.getFromJsonNode(PaginationHelper.getResult(result.toJsonNode(), pagination)); return Response.status(Status.OK).entity(result).header(GlobalDataRest.X_REQUEST_ID, requestId) .header(IhmDataRest.X_OFFSET, pagination.getOffset()) .header(IhmDataRest.X_LIMIT, pagination.getLimit()).build(); } catch (final VitamException e) { LOGGER.error("Bad request Exception ", e); return Response.status(Status.BAD_REQUEST).header(GlobalDataRest.X_REQUEST_ID, requestId).build(); } } else { requestId = GUIDFactory.newRequestIdGUID(tenantId).toString(); ParametersChecker.checkParameter(SEARCH_CRITERIA_MANDATORY_MSG, options); try (final AdminExternalClient adminClient = AdminExternalClientFactory.getInstance().getClient()) { SanityChecker.checkJsonAll(JsonHandler.toJsonNode(options)); final Map<String, String> optionsMap = JsonHandler.getMapStringFromString(options); final JsonNode query = DslQueryHelper.createSingleQueryDSL(optionsMap); result = adminClient.findDocuments(AdminCollections.RULES, query, getTenantId(headers)); // save result PaginationHelper.setResult(sessionId, result.toJsonNode()); // pagination result = RequestResponseOK.getFromJsonNode(PaginationHelper.getResult(result.toJsonNode(), pagination)); return Response.status(Status.OK).entity(result).build(); } catch (final InvalidCreateOperationException | InvalidParseOperationException e) { LOGGER.error("Bad request Exception ", e); return Response.status(Status.BAD_REQUEST).build(); } catch (final AccessExternalClientNotFoundException e) { LOGGER.error("AdminManagementClient NOT FOUND Exception ", e); return Response.status(Status.NOT_FOUND).build(); } catch (final Exception e) { LOGGER.error(INTERNAL_SERVER_ERROR_MSG); return Response.status(Status.INTERNAL_SERVER_ERROR).build(); } } } /** * @param headers HTTP Headers * @param ruleId id of rule * @param options the queries for searching * @return Response */ @POST @Path("/admin/rules/{id_rule}") @Produces(MediaType.APPLICATION_JSON) public Response getRuleById(@Context HttpHeaders headers, @PathParam("id_rule") String ruleId, String options) { RequestResponse result = null; try (final AdminExternalClient adminClient = AdminExternalClientFactory.getInstance().getClient()) { ParametersChecker.checkParameter(SEARCH_CRITERIA_MANDATORY_MSG, options); SanityChecker.checkJsonAll(JsonHandler.toJsonNode(options)); ParametersChecker.checkParameter("rule Id is mandatory", ruleId); SanityChecker.checkJsonAll(JsonHandler.toJsonNode(ruleId)); result = adminClient.findDocumentById(AdminCollections.RULES, ruleId, getTenantId(headers)); return Response.status(Status.OK).entity(result).build(); } catch (final InvalidParseOperationException e) { LOGGER.error(BAD_REQUEST_EXCEPTION_MSG, e); return Response.status(Status.BAD_REQUEST).build(); } catch (final AccessExternalClientNotFoundException e) { LOGGER.error("AdminManagementClient NOT FOUND Exception ", e); return Response.status(Status.NOT_FOUND).build(); } catch (final Exception e) { LOGGER.error(INTERNAL_SERVER_ERROR_MSG, e); return Response.status(Status.INTERNAL_SERVER_ERROR).build(); } } /*** * check the referential rules * * @param headers HTTP Headers * @param input the rules file csv * @return If the rules file is valid, return ok. If not, return the list of errors */ @POST @Path("/rules/check") @Consumes(MediaType.APPLICATION_OCTET_STREAM) @Produces(MediaType.APPLICATION_JSON) public Response checkRefRule(@Context HttpHeaders headers, InputStream input) { Response response; try (final AdminExternalClient adminClient = AdminExternalClientFactory.getInstance().getClient()) { try { response = adminClient.checkDocuments(AdminCollections.RULES, input, getTenantId(headers)); switch (response.getStatusInfo().getFamily()) { case SUCCESSFUL: return Response.status(Status.OK).build(); default: return Response.status(Status.BAD_REQUEST).build(); } } catch (final AccessExternalClientException e) { return Response.status(Status.FORBIDDEN).build(); } catch (final Exception e) { LOGGER.error(e); return Response.status(Status.INTERNAL_SERVER_ERROR).build(); } finally { StreamUtils.closeSilently(input); } } } /** * Upload the referential rules in the base * * @param headers HTTP Headers * @param input the format file CSV * @return Response */ @POST @Path("/rules/upload") @Consumes(MediaType.APPLICATION_OCTET_STREAM) @Produces(MediaType.APPLICATION_JSON) public Response uploadRefRule(@Context HttpHeaders headers, InputStream input) { Response response = null; try (final AdminExternalClient adminClient = AdminExternalClientFactory.getInstance().getClient()) { try { response = adminClient.createDocuments(AdminCollections.RULES, input, getTenantId(headers)); switch (response.getStatusInfo().getFamily()) { case SUCCESSFUL: return Response.status(Status.OK).build(); default: return Response.status(Status.INTERNAL_SERVER_ERROR).build(); } } catch (final AccessExternalClientException e) { return Response.status(Status.FORBIDDEN).entity(e.getMessage()).build(); } catch (final Exception e) { LOGGER.error(INTERNAL_SERVER_ERROR_MSG, e); return Response.status(Status.INTERNAL_SERVER_ERROR).build(); } finally { adminClient.consumeAnyEntityAndClose(response); StreamUtils.closeSilently(input); } } } /** * Get the action registers filtered with option query * * @param headers HTTP header needed for the request: X-TENANT-ID (mandatory), X-LIMIT/X-OFFSET (not mandatory) * @param sessionId json session id from shiro * @param options the queries for searching * @return Response */ @POST @Path("/admin/accession-register") @Produces(MediaType.APPLICATION_JSON) public Response getAccessionRegister(@Context HttpHeaders headers, @CookieParam("JSESSIONID") String sessionId, String options) { ParametersChecker.checkParameter("cookie is mandatory", sessionId); String requestId = null; RequestResponse result = null; OffsetBasedPagination pagination = null; try { pagination = new OffsetBasedPagination(headers); } catch (final VitamException e) { LOGGER.error("Bad request Exception ", e); return Response.status(Status.BAD_REQUEST).build(); } final List<String> requestIds = HttpHeaderHelper.getHeaderValues(headers, IhmWebAppHeader.REQUEST_ID.name()); Integer tenantId = getTenantId(headers); if (requestIds != null) { requestId = requestIds.get(0); // get result from shiro session try { result = RequestResponseOK.getFromJsonNode(PaginationHelper.getResult(sessionId, pagination)); return Response.status(Status.OK).entity(result).header(GlobalDataRest.X_REQUEST_ID, requestId) .header(IhmDataRest.X_OFFSET, pagination.getOffset()) .header(IhmDataRest.X_LIMIT, pagination.getLimit()).build(); } catch (final VitamException e) { LOGGER.error("Bad request Exception ", e); return Response.status(Status.BAD_REQUEST).header(GlobalDataRest.X_REQUEST_ID, requestId).build(); } } else { requestId = GUIDFactory.newRequestIdGUID(tenantId).toString(); ParametersChecker.checkParameter(SEARCH_CRITERIA_MANDATORY_MSG, options); try { SanityChecker.checkJsonAll(JsonHandler.toJsonNode(options)); result = UserInterfaceTransactionManager.findAccessionRegisterSummary(options, getTenantId(headers)); // save result PaginationHelper.setResult(sessionId, result.toJsonNode()); // pagination result = RequestResponseOK.getFromJsonNode(PaginationHelper.getResult(result.toJsonNode(), pagination)); } catch (final InvalidCreateOperationException | InvalidParseOperationException e) { LOGGER.error("Bad request Exception ", e); return Response.status(Status.BAD_REQUEST).build(); } catch (final AccessExternalClientNotFoundException e) { LOGGER.error("AdminManagementClient NOT FOUND Exception ", e); return Response.status(Status.NOT_FOUND).build(); } catch (final Exception e) { LOGGER.error(INTERNAL_SERVER_ERROR_MSG, e); return Response.status(Status.INTERNAL_SERVER_ERROR).build(); } return Response.status(Status.OK).entity(result).build(); } } /** * Get the detail of an accessionregister matching options query * * @param headers HTTP Headers * @param id * @param options query criteria * @return accession register details */ @POST @Path("/admin/accession-register/{id}/accession-register-detail") @Produces(MediaType.APPLICATION_JSON) public Response getAccessionRegisterDetail(@Context HttpHeaders headers, @PathParam("id") String id, String options) { ParametersChecker.checkParameter(SEARCH_CRITERIA_MANDATORY_MSG, options); RequestResponse result = null; try { SanityChecker.checkJsonAll(JsonHandler.toJsonNode(options)); result = UserInterfaceTransactionManager.findAccessionRegisterDetail(id, options, getTenantId(headers)); } catch (final InvalidCreateOperationException | InvalidParseOperationException e) { LOGGER.error(BAD_REQUEST_EXCEPTION_MSG, e); return Response.status(Status.BAD_REQUEST).build(); } catch (final AccessExternalClientNotFoundException e) { LOGGER.error("AdminManagementClient NOT FOUND Exception ", e); return Response.status(Status.NOT_FOUND).build(); } catch (final Exception e) { LOGGER.error(INTERNAL_SERVER_ERROR_MSG, e); return Response.status(Status.INTERNAL_SERVER_ERROR).build(); } return Response.status(Status.OK).entity(result).build(); } /** * This resource returns all paths relative to a unit * * @param headers HTTP Headers * @param unitId the unit id * @param allParents all parents unit * @return all paths relative to a unit */ @POST @Path("/archiveunit/tree/{id}") @Consumes(MediaType.APPLICATION_JSON) @Produces(MediaType.APPLICATION_JSON) public Response getUnitTree(@Context HttpHeaders headers, @PathParam("id") String unitId, String allParents) { ParametersChecker.checkParameter(SEARCH_CRITERIA_MANDATORY_MSG, unitId); try { SanityChecker.checkJsonAll(JsonHandler.toJsonNode(unitId)); SanityChecker.checkJsonAll(JsonHandler.toJsonNode(allParents)); if (allParents == null || allParents.isEmpty()) { return Response.status(Status.OK).entity(JsonHandler.createArrayNode()).build(); } if (!JsonHandler.getFromString(allParents).isArray()) { throw new VitamException(INVALID_ALL_PARENTS_TYPE_ERROR_MSG); } // 1- Build DSL Query final ArrayNode allParentsArray = (ArrayNode) JsonHandler.getFromString(allParents); final List<String> allParentsList = StreamSupport.stream(allParentsArray.spliterator(), false) .map(p -> new String(p.asText())).collect(Collectors.toList()); final JsonNode preparedDslQuery = DslQueryHelper.createSelectUnitTreeDSLQuery(unitId, allParentsList); // 2- Execute Select Query final RequestResponse parentsDetails = UserInterfaceTransactionManager.searchUnits(preparedDslQuery, getTenantId(headers)); // 3- Build Unit tree (all paths) final JsonNode unitTree = UserInterfaceTransactionManager.buildUnitTree(unitId, parentsDetails.toJsonNode().get(UiConstants.RESULT.getResultCriteria())); return Response.status(Status.OK).entity(unitTree).build(); } catch (InvalidParseOperationException | InvalidCreateOperationException e) { LOGGER.error(BAD_REQUEST_EXCEPTION_MSG, e); return Response.status(Status.BAD_REQUEST).build(); } catch (final AccessExternalClientServerException e) { LOGGER.error(ACCESS_SERVER_EXCEPTION_MSG, e); return Response.status(Status.INTERNAL_SERVER_ERROR).build(); } catch (final AccessExternalClientNotFoundException e) { LOGGER.error(ACCESS_CLIENT_NOT_FOUND_EXCEPTION_MSG, e); return Response.status(Status.NOT_FOUND).build(); } catch (final Exception e) { LOGGER.error(INTERNAL_SERVER_ERROR_MSG, e); return Response.status(Status.INTERNAL_SERVER_ERROR).build(); } } /** * @param headers HTTP Headers * @param object user credentials * @return Response OK if login success */ @POST @Path("login") @Consumes(MediaType.APPLICATION_JSON) @Produces(MediaType.APPLICATION_JSON) public Response login(@Context HttpHeaders headers, JsonNode object) { final Subject subject = ThreadContext.getSubject(); final String username = object.get("token").get("principal").textValue(); final String password = object.get("token").get("credentials").textValue(); if (username == null || password == null) { return Response.status(Status.UNAUTHORIZED).build(); } final UsernamePasswordToken token = new UsernamePasswordToken(username, password); try { subject.login(token); // TODO P1 add access log LOGGER.info("Login success: " + username); } catch (final Exception uae) { LOGGER.debug("Login fail: " + username); return Response.status(Status.UNAUTHORIZED).build(); } return Response.status(Status.OK).build(); } /** * returns the unit life cycle based on its id * * @param headers HTTP Headers * @param unitLifeCycleId the unit id (== unit life cycle id) * @return the unit life cycle */ @GET @Path("/unitlifecycles/{id_lc}") @Produces(MediaType.APPLICATION_JSON) public Response getUnitLifeCycleById(@Context HttpHeaders headers, @PathParam("id_lc") String unitLifeCycleId) { ParametersChecker.checkParameter(SEARCH_CRITERIA_MANDATORY_MSG, unitLifeCycleId); RequestResponse result = null; try { result = UserInterfaceTransactionManager.selectUnitLifeCycleById(unitLifeCycleId, getTenantId(headers)); } catch (final InvalidParseOperationException e) { LOGGER.error(e); return Response.status(Status.BAD_REQUEST).entity(e.getMessage()).build(); } catch (final LogbookClientException e) { LOGGER.error(LOGBOOK_CLIENT_NOT_FOUND_EXCEPTION_MSG, e); return Response.status(Status.NOT_FOUND).build(); } catch (final Exception e) { LOGGER.error(INTERNAL_SERVER_ERROR_MSG, e); return Response.status(Status.INTERNAL_SERVER_ERROR).build(); } return Response.status(Status.OK).entity(result).build(); } /** * returns the object group life cycle based on its id * * @param headers HTTP Headers * @param objectGroupLifeCycleId the object group id (== object group life cycle id) * @return the object group life cycle */ @GET @Path("/objectgrouplifecycles/{id_lc}") @Produces(MediaType.APPLICATION_JSON) public Response getObjectGroupLifeCycleById(@Context HttpHeaders headers, @PathParam("id_lc") String objectGroupLifeCycleId) { ParametersChecker.checkParameter(SEARCH_CRITERIA_MANDATORY_MSG, objectGroupLifeCycleId); RequestResponse result; try { result = UserInterfaceTransactionManager.selectObjectGroupLifeCycleById(objectGroupLifeCycleId, getTenantId(headers)); } catch (final InvalidParseOperationException e) { LOGGER.error(e); return Response.status(Status.BAD_REQUEST).entity(e.getMessage()).build(); } catch (final LogbookClientException e) { LOGGER.error(LOGBOOK_CLIENT_NOT_FOUND_EXCEPTION_MSG, e); return Response.status(Status.NOT_FOUND).build(); } catch (final Exception e) { LOGGER.error(INTERNAL_SERVER_ERROR_MSG, e); return Response.status(Status.INTERNAL_SERVER_ERROR).build(); } return Response.status(Status.OK).entity(result).build(); } /** * Get the workflow operations list for step by step ingest * * @param headers request headers * @return the operations list */ @GET @Path("/operations") @Produces(MediaType.APPLICATION_JSON) public Response listOperationsDetails(@Context HttpHeaders headers) { Response response = null; try (IngestExternalClient client = IngestExternalClientFactory.getInstance().getClient()) { String tenantIdHeader = headers.getHeaderString(GlobalDataRest.X_TENANT_ID); VitamThreadUtils.getVitamSession().setTenantId(Integer.parseInt(tenantIdHeader)); response = client.listOperationsDetails(); return Response.fromResponse(response).build(); } catch (VitamClientException e) { LOGGER.error(INTERNAL_SERVER_ERROR_MSG, e); return Response.status(Status.INTERNAL_SERVER_ERROR).build(); } } /** * Update the status of an operation. * * @param headers contain X-Action and X-Context-ID * @param id operation identifier * @return http response */ @Path("operations/{id}") @PUT @Produces(MediaType.APPLICATION_OCTET_STREAM) public Response updateWorkFlowStatus(@Context HttpHeaders headers, @PathParam("id") String id) { Response response = null; ParametersChecker.checkParameter("ACTION Request must not be null", headers.getRequestHeader(GlobalDataRest.X_ACTION)); final String xAction = headers.getRequestHeader(GlobalDataRest.X_ACTION).get(0); String tenantIdHeader = headers.getHeaderString(GlobalDataRest.X_TENANT_ID); VitamThreadUtils.getVitamSession().setTenantId(Integer.parseInt(tenantIdHeader)); try (IngestExternalClient ingestExternalClient = IngestExternalClientFactory.getInstance().getClient()) { response = ingestExternalClient.updateOperationActionProcess(xAction, id); final String globalExecutionStatus = response.getHeaderString(GlobalDataRest.X_GLOBAL_EXECUTION_STATUS); final boolean isCompletedProcess = globalExecutionStatus != null && (ProcessExecutionStatus.COMPLETED .equals(ProcessExecutionStatus.valueOf(globalExecutionStatus)) || ProcessExecutionStatus.FAILED .equals(ProcessExecutionStatus.valueOf(globalExecutionStatus))); if (isCompletedProcess) { final InputStream inputStream = (InputStream) response.getEntity(); if (inputStream != null) { File file = PropertiesUtils.fileFromTmpFolder("ATR_" + id + ".xml"); try (FileOutputStream fileOutputStream = new FileOutputStream(file)) { StreamUtils.copy(inputStream, fileOutputStream); } catch (IOException e) { throw new VitamClientException("Error during ATR generation"); } LOGGER.debug("DEBUG: " + file + ":" + file.length()); return Response.status(response.getStatus()) .entity(new VitamStreamingOutput(file, false)) .type(MediaType.APPLICATION_OCTET_STREAM_TYPE) .header("Content-Disposition", "attachment; filename=ATR_" + id + ".xml") .header(GlobalDataRest.X_REQUEST_ID, id) .header(GlobalDataRest.X_GLOBAL_EXECUTION_STATUS, globalExecutionStatus) .build(); } else { throw new VitamClientException("No ArchiveTransferReply found in response from Server"); } } return response; } catch (final ProcessingException e) { // if there is an unauthorized action LOGGER.error(e); return Response.status(Status.UNAUTHORIZED).entity(e.getMessage()).build(); } catch (InternalServerException | VitamClientException e) { LOGGER.error(e); return Response.status(Status.INTERNAL_SERVER_ERROR).entity(e.getMessage()).build(); } catch (BadRequestException e) { LOGGER.error(e); return Response.status(Status.BAD_REQUEST).entity(e.getMessage()).build(); } finally { DefaultAdminClient.staticConsumeAnyEntityAndClose(response); } } @DELETE @Path("/operations/{id}") public Response cancelProcess(@Context HttpHeaders headers, @PathParam("id") String id) { String tenantIdHeader = headers.getHeaderString(GlobalDataRest.X_TENANT_ID); VitamThreadUtils.getVitamSession().setTenantId(Integer.parseInt(tenantIdHeader)); try (IngestExternalClient ingestExternalClient = IngestExternalClientFactory.getInstance().getClient()) { return ingestExternalClient.cancelOperationProcessExecution(id); // ItemStatus itemStatus = // ingestExternalClient.cancelOperationProcessExecution(id); // // // TODO check null value // return Response.status(Status.OK) // .header(GlobalDataRest.X_GLOBAL_EXECUTION_STATUS, itemStatus.getGlobalExecutionStatus().toString()) // .build(); } catch (InternalServerException | VitamClientException | ProcessingException e) { LOGGER.error(e); return Response.status(Status.INTERNAL_SERVER_ERROR).entity(e.getMessage()).build(); } catch (BadRequestException e) { LOGGER.error(e); return Response.status(Status.UNAUTHORIZED).entity(e.getMessage()).build(); } } private Integer getTenantId(HttpHeaders headers) { // TODO Error check ? Throw error or put tenant Id 0 Integer tenantId = 0; String tenantIdHeader = headers.getHeaderString(GlobalDataRest.X_TENANT_ID); if (tenantIdHeader != null) { try { tenantId = Integer.parseInt(tenantIdHeader); } catch (NumberFormatException e) { // TODO Throw error or log something ? // Do Nothing : Put 0 as tenant Id } } VitamThreadUtils.getVitamSession().setTenantId(tenantId); return tenantId; } private File downloadAndSaveATR(String guid, Integer tenantId) throws VitamClientException, IngestExternalException { File file = null; final Response response; try (IngestExternalClient ingestExternalClient = IngestExternalClientFactory.getInstance().getClient()) { response = ingestExternalClient .downloadObjectAsync(guid, IngestCollection.REPORTS, tenantId); InputStream inputStream = response.readEntity(InputStream.class); ; if (inputStream != null) { file = PropertiesUtils.fileFromTmpFolder("ATR_" + guid + ".xml"); try (FileOutputStream fileOutputStream = new FileOutputStream(file)) { StreamUtils.copy(inputStream, fileOutputStream); } catch (IOException e) { throw new VitamClientException("Error during ATR generation"); } } } return file; } }