StorageItem Lifecycle

Dari provides two callbacks for file-saving events: before saving the file and after saving the file.

Before Save

The beforeSave event, defined by Interface StorageItemBeforeSave, occurs immediately prior to saving a file. In a typical scenario, you interrupt this event as part of processing a file passed to the doRequest method available in Class StorageItemFilter.

A best practice in a web application that allows file uploads is to verify that the uploaded file is of the type expected. For example, if you allow users to upload an image, you should verify that the file is indeed an image and not a text or PDF file. The following example ensures that an uploaded file has a permitted MIME type for images.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
package upload.processors;

import com.psddev.dari.util.StorageItem;
import com.psddev.dari.util.StorageItemBeforeSave;
import com.psddev.dari.util.StorageItemUploadPart;

import java.io.IOException;
import java.util.ArrayList;
import java.util.List;

public class LocalStorageItemBeforeSave implements StorageItemBeforeSave {

    @Override
    public void beforeSave(StorageItem storageItem, StorageItemUploadPart storageItemUploadPart) throws IOException {

        List<String> allowedTypes = new ArrayList<>();
        allowedTypes.add("image/jpeg");
        allowedTypes.add("image/png");
        allowedTypes.add("image/gif");

        String uploadedType = storageItem.getContentType();

        if (!allowedTypes.contains(uploadedType)) {
            throw new IOException("[" + uploadedType + "] is not one of the allowed MIME types!");
        }
    }
}

In the previous snippet—

  • Lines 16–19 construct a list of allowed MIME types for an uploaded file.
  • Line 21 retrieves the MIME type of the uploaded file.
  • Lines 23–25 perform exception handling if the uploaded file does not have a permitted MIME type.

After Save

The afterSave event, defined by Interface StorageItemAfterSave, occurs immediately after saving a file. In a typical scenario, you interrupt this event as part of processing a file passed to the doRequest method available in Class StorageItemFilter.

Some project implementations make backups of uploaded files, a good strategy to increase reliability or for forensic investigations. The following example copies a saved file to another disk at a particular mount point.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
package upload.processors;

import com.psddev.dari.util.Settings;
import com.psddev.dari.util.StorageItem;
import com.psddev.dari.util.StorageItemAfterSave;
import org.apache.commons.io.FileUtils;

import java.io.File;
import java.io.IOException;

public class LocalStorageItemAfterSave implements StorageItemAfterSave {

    @Override
    public void afterSave(StorageItem storageItem) throws IOException {

        String staticPath = Settings.get(String.class, "dari/storage/localstorage/rootPath");

        String dynamicPath = storageItem.getPath();
        String fullPath = staticPath + "/" + dynamicPath;

        File source = new File(fullPath);
        File dest = new File("/path/to/mount/point/" + dynamicPath);
        try {
            FileUtils.copyFile(source, dest);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

In the previous snippet—

  • Line 16 retrieves the static portion of the path where Dari saved the file.
  • Line 18 retrieves the dynamic portion of the path, including the file name, where Dari saved the file. (For an explanation of static and dynamic portions of storage item paths, see the illustration in Configuring StorageItem.)
  • Line 19 combines the static path with the dynamic path to get the full path to the source file.
  • Line 22 constructs a new destination path for the copied file. The static portion is the hard-coded path to the mount point, and the dynamic portion is a replica from the source file.

For example, suppose the Tomcat configuration includes the following entry:

<Environment name="dari/storage/localstorage/rootPath" type="java.lang.String" value="/home/dariuser/storage" />

Also suppose the following:

  • Your application uses a path generator that creates a dynamic path roses/are/red/.
  • You uploaded the file dragon-slayer.jpg.
  • Your backup disk is mounted at /mnt/huge-backup-disk/

The previous code snippet is equivalent to the following command:

cp /home/dariuser/storage/roses/are/red/dragon-slayer.jpg /mnt/huge-backup-disk/roses/are/red/dragon-slayer.jpg

Customizing Storage Path Generation

Referring to the illustration in Configuring StorageItem, Dari creates a default dynamic path of the form xx/xx/xxxxxxxxxxxxxxxxxxxxxxxxxxxx/ that is unique for each saved storage item. The dynamic path is derived from a random Java UUID.

You can customize how Dari generates the dynamic path, such as using a different random-number generator or even generating a static directory structure. In the following example you implement Interface StorageItemPathGenerator to create a directory structure parallel to calendar dates.

Custom path generators are available only to storage items instantiated from Class StorageItemFilter. If you instantiate a storage item from one of the classes implementing Interface StorageItem, you specify the custom path and file name in the setPath method.

Step 1: Implement a Path Generator

The following snippet retrieves the current date and makes a path of the form YYYY/MM/DD/filename.png.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
package upload.processors;

import com.psddev.dari.util.*;
import java.time.LocalDateTime;

public class CalendarDirectoryStorageItemPathGenerator implements StorageItemPathGenerator {

    @Override
    public String createPath(String fileName) {

        LocalDateTime currentDate = LocalDateTime.now();

        String calendarPath = currentDate.getYear() + "/" + currentDate.getMonthValue() + "/" + currentDate.getDayOfMonth();
        calendarPath += "/" + fileName;
        return calendarPath;

    }
}

In the previous snippet—

  • Line 13 creates a path string using YYYY/MM/DD format.
  • Line 14 appends to the path the passed uploaded filename.

Step 2: Configure the Path Generator

1
2
3
4
5
<Environment name="dari/storage/local/class" value="com.psddev.dari.util.LocalStorageItem" type="java.lang.String" />
<Environment name="dari/storage/local/baseUrl" value="/storage" type="java.lang.String" />
<Environment name="dari/storage/local/originBaseUrl" value="http://localhost/storage" type="java.lang.String" />
<Environment name="dari/storage/local/rootPath" value="/servers/training/www/storage/" type="java.lang.String" />
<Environment name="dari/upload/local/pathGenerator" value="upload.processors.CalendarDirectoryStorageItemPathGenerator" type="java.lang.String" />

In the previous snippet—

  • Line 4 specifies the static portion of the path where Dari saves the storage item.
  • Line 5 specifies that the class that generates the dynamic portion of the path where Dari saves the storage item. The value is the fully qualified class name that includes the package name.

Referring to the previous snippet, after you upload the file dragon-slayer.png, Dari saves the image in an absolute path similar to /servers/training/www/storage/2017/02/28/dragon-slayer.png.

See also:

Creating StorageItems

Upload Handling

Configuring StorageItem