de.metas.ui.web.window.controller.WindowRestController.java Source code

Java tutorial

Introduction

Here is the source code for de.metas.ui.web.window.controller.WindowRestController.java

Source

package de.metas.ui.web.window.controller;

import java.io.IOException;
import java.util.Collection;
import java.util.List;
import java.util.Set;

import org.adempiere.service.IAttachmentBL;
import org.adempiere.util.GuavaCollectors;
import org.adempiere.util.Services;
import org.compiere.model.MAttachmentEntry;
import org.slf4j.Logger;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.DeleteMapping;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.multipart.MultipartFile;

import com.google.common.collect.ImmutableList;

import de.metas.adempiere.report.jasper.OutputType;
import de.metas.logging.LogManager;
import de.metas.process.ProcessExecutionResult;
import de.metas.process.ProcessInfo;
import de.metas.ui.web.config.WebConfig;
import de.metas.ui.web.exceptions.EntityNotFoundException;
import de.metas.ui.web.process.DocumentPreconditionsAsContext;
import de.metas.ui.web.process.descriptor.ProcessDescriptorsFactory;
import de.metas.ui.web.process.descriptor.WebuiRelatedProcessDescriptor;
import de.metas.ui.web.process.json.JSONDocumentActionsList;
import de.metas.ui.web.session.UserSession;
import de.metas.ui.web.window.datatypes.DocumentPath;
import de.metas.ui.web.window.datatypes.DocumentType;
import de.metas.ui.web.window.datatypes.json.JSONAttachment;
import de.metas.ui.web.window.datatypes.json.JSONDocument;
import de.metas.ui.web.window.datatypes.json.JSONDocumentChangedEvent;
import de.metas.ui.web.window.datatypes.json.JSONDocumentLayout;
import de.metas.ui.web.window.datatypes.json.JSONDocumentReferencesList;
import de.metas.ui.web.window.datatypes.json.JSONLookupValuesList;
import de.metas.ui.web.window.datatypes.json.JSONOptions;
import de.metas.ui.web.window.descriptor.DetailId;
import de.metas.ui.web.window.descriptor.DocumentEntityDescriptor;
import de.metas.ui.web.window.descriptor.DocumentLayoutDescriptor;
import de.metas.ui.web.window.descriptor.DocumentLayoutDetailDescriptor;
import de.metas.ui.web.window.model.Document;
import de.metas.ui.web.window.model.DocumentCollection;
import de.metas.ui.web.window.model.DocumentReference;
import de.metas.ui.web.window.model.DocumentReferencesService;
import de.metas.ui.web.window.model.IDocumentChangesCollector.ReasonSupplier;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiParam;

/*
 * #%L
 * metasfresh-webui-api
 * %%
 * Copyright (C) 2016 metas GmbH
 * %%
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as
 * published by the Free Software Foundation, either version 2 of the
 * License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public
 * License along with this program. If not, see
 * <http://www.gnu.org/licenses/gpl-2.0.html>.
 * #L%
 */

@Api
@RestController
@RequestMapping(value = WindowRestController.ENDPOINT)
public class WindowRestController {
    public static final String ENDPOINT = WebConfig.ENDPOINT_ROOT + "/window";
    private static final String PARAM_Advanced = "advanced";
    private static final String PARAM_Advanced_DefaultValue = "false";
    private static final String PARAM_FieldsList = "fields";

    private static final Logger logger = LogManager.getLogger(WindowRestController.class);

    private static final ReasonSupplier REASON_Value_DirectSetFromCommitAPI = () -> "direct set from commit API";

    @Autowired
    private UserSession userSession;

    @Autowired
    private DocumentCollection documentCollection;

    @Autowired
    private ProcessDescriptorsFactory processDescriptorFactory;

    @Autowired
    private DocumentReferencesService documentReferencesService;

    private JSONOptions.Builder newJSONOptions() {
        return JSONOptions.builder().setUserSession(userSession);
    }

    @RequestMapping(value = "/{windowId}/layout", method = RequestMethod.GET)
    public JSONDocumentLayout getLayout(@PathVariable("windowId") final int adWindowId //
            ,
            @RequestParam(name = PARAM_Advanced, required = false, defaultValue = PARAM_Advanced_DefaultValue) final boolean advanced //
    ) {
        userSession.assertLoggedIn();

        final DocumentLayoutDescriptor layout = documentCollection.getDocumentDescriptorFactory()
                .getDocumentDescriptor(adWindowId).getLayout();

        final JSONOptions jsonOpts = newJSONOptions().setShowAdvancedFields(advanced).build();

        return JSONDocumentLayout.ofHeaderLayout(layout, jsonOpts);
    }

    @RequestMapping(value = "/{windowId}/{tabId}/layout", method = RequestMethod.GET)
    public JSONDocumentLayout getLayout(@PathVariable("windowId") final int adWindowId //
            , @PathVariable("tabId") final String tabIdStr //
            ,
            @RequestParam(name = PARAM_Advanced, required = false, defaultValue = PARAM_Advanced_DefaultValue) final boolean advanced //
    ) {
        userSession.assertLoggedIn();

        final DocumentLayoutDescriptor layout = documentCollection.getDocumentDescriptorFactory()
                .getDocumentDescriptor(adWindowId).getLayout();

        final JSONOptions jsonOpts = newJSONOptions().setShowAdvancedFields(advanced).build();

        final DetailId detailId = DetailId.fromJson(tabIdStr);
        final DocumentLayoutDetailDescriptor detailLayout = layout.getDetail(detailId);
        return JSONDocumentLayout.ofDetailTab(detailLayout, jsonOpts);
    }

    @RequestMapping(value = "/{windowId}/{documentId}", method = RequestMethod.GET)
    public List<JSONDocument> getData(@PathVariable("windowId") final int adWindowId //
            , @PathVariable("documentId") final String documentIdStr //
            ,
            @RequestParam(name = PARAM_FieldsList, required = false) @ApiParam("comma separated field names") final String fieldsListStr //
            ,
            @RequestParam(name = PARAM_Advanced, required = false, defaultValue = PARAM_Advanced_DefaultValue) final boolean advanced //
    ) {
        final DocumentPath documentPath = DocumentPath.rootDocumentPath(DocumentType.Window, adWindowId,
                documentIdStr);
        return getData(documentPath, fieldsListStr, advanced);
    }

    @RequestMapping(value = "/{windowId}/{documentId}/{tabId}", method = RequestMethod.GET)
    public List<JSONDocument> getData(@PathVariable("windowId") final int adWindowId //
            , @PathVariable("documentId") final String documentIdStr //
            , @PathVariable("tabId") final String tabIdStr //
            ,
            @RequestParam(name = PARAM_FieldsList, required = false) @ApiParam("comma separated field names") final String fieldsListStr //
            ,
            @RequestParam(name = PARAM_Advanced, required = false, defaultValue = PARAM_Advanced_DefaultValue) final boolean advanced //
    ) {
        final DocumentPath documentPath = DocumentPath.includedDocumentPath(DocumentType.Window, adWindowId,
                documentIdStr, tabIdStr);
        return getData(documentPath, fieldsListStr, advanced);
    }

    @RequestMapping(value = "/{windowId}/{documentId}/{tabId}/{rowId}", method = RequestMethod.GET)
    public List<JSONDocument> getData(@PathVariable("windowId") final int adWindowId //
            , @PathVariable("documentId") final String documentIdStr //
            , @PathVariable("tabId") final String tabIdStr //
            , @PathVariable("rowId") final String rowIdStr //
            ,
            @RequestParam(name = PARAM_FieldsList, required = false) @ApiParam("comma separated field names") final String fieldsListStr //
            ,
            @RequestParam(name = PARAM_Advanced, required = false, defaultValue = PARAM_Advanced_DefaultValue) final boolean advanced //
    ) {
        final DocumentPath documentPath = DocumentPath.includedDocumentPath(DocumentType.Window, adWindowId,
                documentIdStr, tabIdStr, rowIdStr);
        return getData(documentPath, fieldsListStr, advanced);
    }

    List<JSONDocument> getData(final DocumentPath documentPath, final String fieldsListStr,
            final boolean advanced) {
        userSession.assertLoggedIn();

        //
        // Retrieve and return the documents
        final List<Document> documents = documentCollection.getDocuments(documentPath);
        return JSONDocument.ofDocumentsList(documents,
                newJSONOptions().setShowAdvancedFields(advanced).setDataFieldsList(fieldsListStr).build());

    }

    @RequestMapping(value = "/{windowId}/{documentId}", method = RequestMethod.PATCH)
    public List<JSONDocument> patchRootDocument(@PathVariable("windowId") final int adWindowId //
            , @PathVariable("documentId") final String documentIdStr //
            ,
            @RequestParam(name = PARAM_Advanced, required = false, defaultValue = PARAM_Advanced_DefaultValue) final boolean advanced //
            , @RequestBody final List<JSONDocumentChangedEvent> events) {
        final DocumentPath documentPath = DocumentPath.builder().setDocumentType(DocumentType.Window, adWindowId)
                .setDocumentId(documentIdStr).allowNewDocumentId().build();

        return patchDocument(documentPath, advanced, events);
    }

    @RequestMapping(value = "/{windowId}/{documentId}/{tabId}/{rowId}", method = RequestMethod.PATCH)
    public List<JSONDocument> patchIncludedDocument(@PathVariable("windowId") final int adWindowId //
            , @PathVariable("documentId") final String documentIdStr //
            , @PathVariable("tabId") final String detailIdStr //
            , @PathVariable("rowId") final String rowIdStr //
            ,
            @RequestParam(name = PARAM_Advanced, required = false, defaultValue = PARAM_Advanced_DefaultValue) final boolean advanced //
            , @RequestBody final List<JSONDocumentChangedEvent> events) {
        final DocumentPath documentPath = DocumentPath.builder().setDocumentType(DocumentType.Window, adWindowId)
                .setDocumentId(documentIdStr).setDetailId(detailIdStr).setRowId(rowIdStr).allowNewRowId().build();

        return patchDocument(documentPath, advanced, events);
    }

    List<JSONDocument> patchDocument(final DocumentPath documentPath, final boolean advanced,
            final List<JSONDocumentChangedEvent> events) {
        userSession.assertLoggedIn();

        final JSONOptions jsonOpts = newJSONOptions().setShowAdvancedFields(advanced).build();

        return Execution.callInNewExecution("window.commit", () -> patchDocument0(documentPath, events, jsonOpts));

    }

    private List<JSONDocument> patchDocument0(final DocumentPath documentPath,
            final List<JSONDocumentChangedEvent> events, final JSONOptions jsonOpts) {
        //
        // Fetch the document in writing mode
        final Document document = documentCollection.getOrCreateDocumentForWriting(documentPath);

        //
        // Apply changes
        document.processValueChanges(events, REASON_Value_DirectSetFromCommitAPI);

        // Push back the changed document
        documentCollection.commit(document);

        //
        // Make sure all events were collected for the case when we just created the new document
        // FIXME: this is a workaround and in case we find out all events were collected, we just need to remove this.
        if (documentPath.isNewDocument()) {
            logger.debug("Checking if we collected all events for the new document");
            final Set<String> collectedFieldNames = Execution.getCurrentDocumentChangesCollector()
                    .collectFrom(document, REASON_Value_DirectSetFromCommitAPI);
            if (!collectedFieldNames.isEmpty()) {
                logger.warn(
                        "We would expect all events to be auto-magically collected but it seems that not all of them were collected!"
                                + "\n Missed (but collected now) field names were: {}" //
                                + "\n Document path: {}" + "\n Patch requests: {}",
                        collectedFieldNames, documentPath, events);
            }
        }

        //
        // Return the changes
        return JSONDocument.ofEvents(Execution.getCurrentDocumentChangesCollector(), jsonOpts);
    }

    @RequestMapping(value = "/{windowId}/{documentId}", method = RequestMethod.DELETE)
    public List<JSONDocument> deleteRootDocument(@PathVariable("windowId") final int adWindowId //
            , @PathVariable("documentId") final String documentId //
    ) {
        final DocumentPath documentPath = DocumentPath.rootDocumentPath(DocumentType.Window, adWindowId,
                documentId);
        return deleteDocuments(ImmutableList.of(documentPath));
    }

    @RequestMapping(value = "/{windowId}", method = RequestMethod.DELETE)
    public List<JSONDocument> deleteRootDocumentsList(@PathVariable("windowId") final int adWindowId //
            , @RequestParam(name = "ids") @ApiParam("comma separated documentIds") final String idsListStr //
    ) {
        final List<DocumentPath> documentPaths = DocumentPath.rootDocumentPathsList(DocumentType.Window, adWindowId,
                idsListStr);
        if (documentPaths.isEmpty()) {
            throw new IllegalArgumentException("No ids provided");
        }

        return deleteDocuments(documentPaths);
    }

    @RequestMapping(value = "/{windowId}/{documentId}/{tabId}/{rowId}", method = RequestMethod.DELETE)
    public List<JSONDocument> deleteIncludedDocument(@PathVariable("windowId") final int adWindowId //
            , @PathVariable("documentId") final String documentId //
            , @PathVariable("tabId") final String tabId //
            , @PathVariable("rowId") final String rowId //
    ) {
        final DocumentPath documentPath = DocumentPath.includedDocumentPath(DocumentType.Window, adWindowId,
                documentId, tabId, rowId);
        return deleteDocuments(ImmutableList.of(documentPath));
    }

    @RequestMapping(value = "/{windowId}/{documentId}/{tabId}", method = RequestMethod.DELETE)
    public List<JSONDocument> deleteIncludedDocumentsList(@PathVariable("windowId") final int adWindowId //
            , @PathVariable("documentId") final String documentId //
            , @PathVariable("tabId") final String tabId //
            , @RequestParam(name = "ids") @ApiParam("comma separated rowIds") final String rowIdsListStr //
    ) {
        final DocumentPath documentPath = DocumentPath.builder().setDocumentType(DocumentType.Window, adWindowId)
                .setDocumentId(documentId).setDetailId(tabId).setRowIdsList(rowIdsListStr).build();
        if (documentPath.getRowIds().isEmpty()) {
            throw new IllegalArgumentException("No rowId(s) specified");
        }
        return deleteDocuments(ImmutableList.of(documentPath));
    }

    List<JSONDocument> deleteDocuments(final List<DocumentPath> documentPaths) {
        userSession.assertLoggedIn();

        final JSONOptions jsonOpts = newJSONOptions().setShowAdvancedFields(false).build();

        return Execution.callInNewExecution("window.delete", () -> {
            documentCollection.deleteAll(documentPaths);
            return JSONDocument.ofEvents(Execution.getCurrentDocumentChangesCollector(), jsonOpts);
        });
    }

    /**
     * Typeahead for root document's field
     */
    @RequestMapping(value = "/{windowId}/{documentId}/attribute/{fieldName}/typeahead", method = RequestMethod.GET)
    public JSONLookupValuesList getDocumentFieldTypeahead(@PathVariable("windowId") final int adWindowId //
            , @PathVariable("documentId") final String documentId //
            , @PathVariable("fieldName") final String fieldName //
            , @RequestParam(name = "query", required = true) final String query //
    ) {
        final DocumentPath documentPath = DocumentPath.rootDocumentPath(DocumentType.Window, adWindowId,
                documentId);
        return getDocumentFieldTypeahead(documentPath, fieldName, query);
    }

    /**
     * Typeahead for included document's field
     */
    @RequestMapping(value = "/{windowId}/{documentId}/{tabId}/{rowId}/attribute/{fieldName}/typeahead", method = RequestMethod.GET)
    public JSONLookupValuesList getDocumentFieldTypeahead(@PathVariable("windowId") final int adWindowId //
            , @PathVariable("documentId") final String documentId //
            , @PathVariable("tabId") final String tabId //
            , @PathVariable("rowId") final String rowId //
            , @PathVariable("fieldName") final String fieldName //
            , @RequestParam(name = "query", required = true) final String query //
    ) {
        final DocumentPath documentPath = DocumentPath.includedDocumentPath(DocumentType.Window, adWindowId,
                documentId, tabId, rowId);
        return getDocumentFieldTypeahead(documentPath, fieldName, query);
    }

    /** Typeahead: unified implementation */
    JSONLookupValuesList getDocumentFieldTypeahead(final DocumentPath documentPath, final String fieldName,
            final String query) {
        userSession.assertLoggedIn();

        return documentCollection.getDocument(documentPath).getFieldLookupValuesForQuery(fieldName, query)
                .transform(JSONLookupValuesList::ofLookupValuesList);
    }

    @RequestMapping(value = "/{windowId}/{documentId}/attribute/{fieldName}/dropdown", method = RequestMethod.GET)
    public JSONLookupValuesList getDocumentFieldDropdown(@PathVariable("windowId") final int adWindowId //
            , @PathVariable("documentId") final String documentId //
            , @PathVariable("fieldName") final String fieldName //
    ) {
        final DocumentPath documentPath = DocumentPath.rootDocumentPath(DocumentType.Window, adWindowId,
                documentId);
        return getDocumentFieldDropdown(documentPath, fieldName);
    }

    @RequestMapping(value = "/{windowId}/{documentId}/{tabId}/{rowId}/attribute/{fieldName}/dropdown", method = RequestMethod.GET)
    public JSONLookupValuesList getDocumentFieldDropdown(@PathVariable("windowId") final int adWindowId //
            , @PathVariable("documentId") final String documentId //
            , @PathVariable("tabId") final String tabId //
            , @PathVariable("rowId") final String rowId //
            , @PathVariable("fieldName") final String fieldName //
    ) {
        final DocumentPath documentPath = DocumentPath.includedDocumentPath(DocumentType.Window, adWindowId,
                documentId, tabId, rowId);
        return getDocumentFieldDropdown(documentPath, fieldName);
    }

    JSONLookupValuesList getDocumentFieldDropdown(final DocumentPath documentPath, final String fieldName) {
        userSession.assertLoggedIn();

        return documentCollection.getDocument(documentPath).getFieldLookupValues(fieldName)
                .transform(JSONLookupValuesList::ofLookupValuesList);
    }

    @RequestMapping(value = "/{windowId}/{documentId}/actions", method = RequestMethod.GET)
    public JSONDocumentActionsList getDocumentActions(@PathVariable("windowId") final int adWindowId //
            , @PathVariable("documentId") final String documentId //
    ) {
        final DocumentPath documentPath = DocumentPath.rootDocumentPath(DocumentType.Window, adWindowId,
                documentId);
        return getDocumentActions(documentPath);
    }

    JSONDocumentActionsList getDocumentActions(final DocumentPath documentPath) {
        userSession.assertLoggedIn();

        final Document document = documentCollection.getDocument(documentPath);
        final DocumentPreconditionsAsContext preconditionsContext = DocumentPreconditionsAsContext.of(document);

        return processDescriptorFactory.streamDocumentRelatedProcesses(preconditionsContext)
                .filter(WebuiRelatedProcessDescriptor::isEnabledOrNotSilent)
                .collect(JSONDocumentActionsList.collect(newJSONOptions().build()));
    }

    @RequestMapping(value = "/{windowId}/{documentId}/references", method = RequestMethod.GET)
    public JSONDocumentReferencesList getDocumentReferences(@PathVariable("windowId") final int adWindowId //
            , @PathVariable("documentId") final String documentId //
    ) {
        final DocumentPath documentPath = DocumentPath.rootDocumentPath(DocumentType.Window, adWindowId,
                documentId);
        return getDocumentReferences(documentPath);
    }

    JSONDocumentReferencesList getDocumentReferences(final DocumentPath documentPath) {
        userSession.assertLoggedIn();

        final Document document = documentCollection.getDocument(documentPath);
        final Collection<DocumentReference> documentReferences = documentReferencesService
                .getDocumentReferences(document).values();
        return JSONDocumentReferencesList.of(documentReferences, newJSONOptions().build());
    }

    @RequestMapping(value = "/{windowId}/{documentId}/print/{filename:.*}", method = RequestMethod.GET)
    public ResponseEntity<byte[]> getDocumentPrint(@PathVariable("windowId") final int adWindowId //
            , @PathVariable("documentId") final String documentId //
            , @PathVariable("filename") final String filename) {
        userSession.assertLoggedIn();

        final DocumentPath documentPath = DocumentPath.rootDocumentPath(DocumentType.Window, adWindowId,
                documentId);

        final Document document = documentCollection.getDocument(documentPath);
        final DocumentEntityDescriptor entityDescriptor = document.getEntityDescriptor();

        final ProcessExecutionResult processExecutionResult = ProcessInfo.builder().setCtx(userSession.getCtx())
                .setAD_Process_ID(entityDescriptor.getPrintProcessId())
                .setRecord(entityDescriptor.getTableName(), document.getDocumentIdAsInt()).setPrintPreview(true)
                .setJRDesiredOutputType(OutputType.PDF)
                //
                .buildAndPrepareExecution().onErrorThrowException().switchContextWhenRunning().executeSync()
                .getResult();

        final byte[] reportData = processExecutionResult.getReportData();
        final String reportContentType = processExecutionResult.getReportContentType();

        final HttpHeaders headers = new HttpHeaders();
        headers.setContentType(MediaType.parseMediaType(reportContentType));
        headers.set(HttpHeaders.CONTENT_DISPOSITION, "inline; filename=\"" + filename + "\"");
        headers.setCacheControl("must-revalidate, post-check=0, pre-check=0");
        final ResponseEntity<byte[]> response = new ResponseEntity<>(reportData, headers, HttpStatus.OK);
        return response;
    }

    /**
     * Attaches a file to given root document.
     *
     * @param adWindowId
     * @param documentId
     * @param file
     * @throws IOException
     */
    @PostMapping("/{windowId}/{documentId}/attachments")
    public void attachFile(@PathVariable("windowId") final int adWindowId //
            , @PathVariable("documentId") final String documentId //
            , @RequestParam("file") final MultipartFile file //
    ) throws IOException {
        userSession.assertLoggedIn();

        final String name = file.getOriginalFilename();
        final byte[] data = file.getBytes();

        final DocumentPath documentPath = DocumentPath.rootDocumentPath(DocumentType.Window, adWindowId,
                documentId);
        final Document document = documentCollection.getDocument(documentPath);

        Services.get(IAttachmentBL.class).createAttachment(document, name, data);
    }

    @GetMapping("/{windowId}/{documentId}/attachments")
    public List<JSONAttachment> getAttachments(@PathVariable("windowId") final int adWindowId //
            , @PathVariable("documentId") final String documentId //
    ) {
        userSession.assertLoggedIn();

        final DocumentPath documentPath = DocumentPath.rootDocumentPath(DocumentType.Window, adWindowId,
                documentId);
        final Document document = documentCollection.getDocument(documentPath);

        return Services.get(IAttachmentBL.class).getEntiresForModel(document).stream().map(JSONAttachment::of)
                .collect(GuavaCollectors.toImmutableList());
    }

    @GetMapping("/{windowId}/{documentId}/attachments/{id}")
    public ResponseEntity<byte[]> getAttachmentById(@PathVariable("windowId") final int adWindowId //
            , @PathVariable("documentId") final String documentId //
            , @PathVariable("id") final int id) {
        userSession.assertLoggedIn();

        final DocumentPath documentPath = DocumentPath.rootDocumentPath(DocumentType.Window, adWindowId,
                documentId);
        final Document document = documentCollection.getDocument(documentPath);

        final MAttachmentEntry entry = Services.get(IAttachmentBL.class).getEntryForModelById(document, id);
        if (entry == null) {
            throw new EntityNotFoundException("No attachment found (ID=" + id + ")");
        }

        final String entryFilename = entry.getFilename();
        final byte[] entryData = entry.getData();
        if (entryData == null || entryData.length == 0) {
            throw new EntityNotFoundException("No attachment found (ID=" + id + ")");
        }

        final String entryContentType = entry.getContentType();

        final HttpHeaders headers = new HttpHeaders();
        headers.setContentType(MediaType.parseMediaType(entryContentType));
        headers.set(HttpHeaders.CONTENT_DISPOSITION, "inline; filename=\"" + entryFilename + "\"");
        headers.setCacheControl("must-revalidate, post-check=0, pre-check=0");
        final ResponseEntity<byte[]> response = new ResponseEntity<>(entryData, headers, HttpStatus.OK);
        return response;
    }

    @DeleteMapping("/{windowId}/{documentId}/attachments/{id}")
    public void deleteAttachmentById(@PathVariable("windowId") final int adWindowId //
            , @PathVariable("documentId") final String documentId //
            , @PathVariable("id") final int id //
    ) {
        userSession.assertLoggedIn();

        final DocumentPath documentPath = DocumentPath.rootDocumentPath(DocumentType.Window, adWindowId,
                documentId);
        final Document document = documentCollection.getDocument(documentPath);

        Services.get(IAttachmentBL.class).deleteEntryForModel(document, id);
    }

}