How to overwrite fileStorageDir? - Inaccessible primary storage

Our app-portal client needs to access images uploaded from web client app. The default fileStorageDir in core module is configured to save the uploads to (on Windows):


<projectRoot>\build\tomcat\work\app-core\filestorage

whereas the app-portal accesses the image in ftl like this:


<img src="../../resources/img/sc/t_0730852951112.jpg" />

Here the image path “…/…/resources/img/sc/t_0730852951112.jpg” is hardcoded to test in app-portal.

Now we can upload jpg files (I can see files at: \build\tomcat\work\app-core\filestorage). We want to use the actual uploaded image instead of hardcoded one.

The highest level of folder, this “…/…/resources…” mechanism, can reach is app-portal folder in tomcat\webapps. It can NOT access \build\tomcat\work\app-core\filestorage where images are uploaded to. So what I thought to do is change the fileStorageDir to \build\tomcat\webapps\app-portal\resources\img.

What I have tried is to add a line in <…>\modules\core\src\app.properties:


cuba.fileStorageDir=/webapps/app-portal/resources/img

Then I got error:


ERROR c.h.c.c.app.filestorage.FileStorage - Inaccessible primary storage at \webapps\app-portal\resources\img

How can I overwrite fileStorageDir to access \build\tomcat\webapps…? Is there any difference to specify fileStorageDir value on Windows and Linux?

Thanks a lot,
-Mike

1 Like

Hi,

First of all, you have to create file storage directory manually. Then you can specify file storage location using absolute or relative path:


cuba.fileStorageDir = ${catalina.base}/demo-file-storage # relative to tomcat root folder
# or
cuba.fileStorageDir = /home/user/work/files # absolute storage location

Hi,

By default, portal does not have a Spring MVC Controller that can expose files or images to web browser. You have to create it manually in your project.

This controller doesn’t have to read files from a disk manually since your application can be deployed in a distributed configuration.

There is a special infrastructure interface FileLoader that can load binary data of files to client. Let’s use it to show images in a portal application. See also: https://doc.cuba-platform.com/manual-6.5/fileLoader.html

First of all we need to create a controller that will load binary data from a middleware when a web browser requests image content:


@Controller
public class PngFilesController {

    private final Logger log = LoggerFactory.getLogger(PngFilesController.class);

    @Inject
    protected UserSessionService userSessionService;

    @Inject
    protected DataService dataService;

    @Inject
    protected FileLoader fileLoader; // file loader is used to load file data from middleware to clients

    @GetMapping("/images/{fileId}")
    public void downloadImage(@PathVariable String fileId,
                              @RequestParam(required = false) Boolean attachment,
                              HttpServletResponse response) throws IOException {
        UUID uuid;
        try {
            uuid = UUID.fromString(fileId);
        } catch (IllegalArgumentException e) {
            response.sendError(HttpStatus.BAD_REQUEST.value(), "Invalid entity ID");
            return;
        }
        LoadContext<FileDescriptor> ctx = LoadContext.create(FileDescriptor.class).setId(uuid);
        FileDescriptor fd = dataService.load(ctx);

        // We will send only PNG files here
        if (fd == null || !"png".equals(fd.getExtension())) {
            response.sendError(HttpServletResponse.SC_FORBIDDEN);
            return;
        }

        // YOU HAVE TO CHECK SECURITY RULES MANUALLY HERE OR SETUP ACCESS GROUP CONSTRAINTS FOR ALL USERS !

        try {
            response.setHeader("Cache-Control", "no-cache");
            response.setHeader("Pragma", "no-cache");
            response.setDateHeader("Expires", 0);
            response.setHeader("Content-Type", getContentType(fd));
            response.setHeader("Content-Disposition", (BooleanUtils.isTrue(attachment) ? "attachment" : "inline")
                    + "; filename=\"" + fd.getName() + "\"");

            ServletOutputStream os = response.getOutputStream();
            try (InputStream is = fileLoader.openStream(fd)) {
                IOUtils.copy(is, os);
                os.flush();
            }
        } catch (Exception e) {
            log.error("Error on downloading the file {}", fileId, e);

            response.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
        }
    }

    protected String getContentType(FileDescriptor fd) {
        if (StringUtils.isEmpty(fd.getExtension())) {
            return FileTypesHelper.DEFAULT_MIME_TYPE;
        }

        return FileTypesHelper.getMIMEType("." + fd.getExtension().toLowerCase());
    }
}

Here we simply load binary data and write it to HTTP response. Please note that you have to setup Access groups (including anonymous user)
with proper security settings or check security manually in the controller.

After that, let’s load all the PNG images from a database and show them on index page.

Portal controller:


@Controller
public class PortalController {
    @Inject
    protected DataService dataService;

    @RequestMapping(value = "/", method = RequestMethod.GET)
    public String index(Model model) {
        // load all .png files from db
        List<FileDescriptor> files = dataService.loadList(LoadContext.create(FileDescriptor.class)
                .setQuery(new LoadContext.Query("select fd from sys$FileDescriptor fd")));
        List<UUID> fileIds = files.stream()
                .filter(fd -> "png".equals(fd.getExtension()))
                .map(BaseUuidEntity::getId)
                .collect(Collectors.toList());

        model.addAttribute("fileIds", fileIds);

        return "index";
    }
}

Add the following code to index.html thymeleaf template:


<h1>All .png images from FileStorage</h1>
<ul th:each="fileId: ${fileIds}">
    <li>
        <img th:src="@{${'images/'+ fileId}}"/>
    </li>
</ul>

The last option we have to set is to setup Spring Security options in portal-security-spring.xml:


    <!-- We will simply enable anonymous access to images,
         but you can decide if want to do it or not -->
    <http pattern="/images/**" security="none"/>

Now we can restart the application and upload a couple of PNG images using web interface.
Finally, open index page of portal - and we will see our images.

You can find complere sample project on GitHub: https://github.com/cuba-labs/portal-images

This is a smart way. I will follow and try it out.

Thank you Yuriy.
-Mike

Yuriy,

Your solution works like a charm. I am able to access uploaded images from portal module.

As related, how to specify the default file storage path in core module’s modules\core\src\app.properties? According to:

https://doc.cuba-platform.com/manual-6.5/file_storage_impl.html

The default root path can be specified in app.properties like:
cuba.fileStorageDir=/uploadedFiles

then I got:
ERROR c.h.c.c.app.filestorage.FileStorage - Inaccessible primary storage at \uploadedFiles

If i do NOT define “cuba.fileStorageDir=” line, I got the uploaded file at:
build\tomcat\work\app-core\filestorage\2017\07\28\test.jpg

What’s the correct way to specify cuba.fileStorageDir value?

Thanks,
-Mike

Thank you very much.