Server

Thumbnail

Updated: December 19, 2024

Documents can have a thumbnail. A thumbnail is a reduced-size version of a picture used to help in recognizing and organizing documents. It will stand for any kind of document according to the type and/or facet.

Custom thumbnail factories can be contributed to the thumbnail service, using its extension point. Thumbnails are then available through this service to define how they will be computed and fetched.

Thumbnail Service Architecture

Here are the different components of the thumbnail feature:

Thumbnail factory on GitHub

Here are Nuxeo thumbnail factory implementations on GitHub:

Registering a New Thumbnail Factory

A thumbnail factory can be registered using the following example extension:

<extension target="org.nuxeo.ecm.core.api.thumbnail.ThumbnailService"
  point="thumbnailFactory">

  <thumbnailFactory name="thumbnailAudioFactory" docType="Audio"
    factoryClass="org.nuxeo.ecm.platform.audio.extension.ThumbnailAudioFactory" />
</extension>

The above thumbnail factories will be used to compute and fetch specific thumbnails for folderish documents on one hand, and audio documents on the other hand. Here are their properties:

  • docType: string identifying the related document type. In the example, the type is "Audio".
  • facet: string identifying the related document facet.
  • factoryClass: string representing the class name of the factory to use.

Notice how the ThumbnailService filters the properties:

  • If the document matches docType, the factory is used (ignoring facet)
  • Then, if the document has the facet, the factory is used.
  • If the document does not match the docType and does not have the facet, the default factory is used.

The default factory is the factory that defines no docType and no facet.

Each factory should implement the interface ThumbnailFactory . This interface contract contains two methods to implement:

  • Blob getThumbnail(DocumentModel doc, CoreSession session): gets the document thumbnail (related to the doc type/facet).
  • Blob computeThumbnail(DocumentModel doc, CoreSession session): computes the thumbnail (related to the document type/facet).

The listener UpdateThumbnailListener is calling YourFactory#computeThumbnail that allows developers to compute the document blob when creating a document and after updating it (if the blob related to this document has been changed).

When computing your thumbnail, UpdateThumbnailListener stores it into a specific metadata thumb:thumbnail provided by the following schema:

<xs:schema xmlns:nxs="http://www.nuxeo.org/ecm/schemas/thumbnail"
  xmlns:xs="http://www.w3.org/2001/XMLSchema" targetNamespace="http://www.nuxeo.org/ecm/schemas/thumbnail">

  <xs:include schemaLocation="core-types.xsd" />

  <xs:element name="thumbnail" type="nxs:content" />

</xs:schema>

This process can be useful to avoid lazy loading when displaying documents list.

Picture Thumbnail Example

Here is an example with the picture thumbnail factory, which is fetching a image existing into the picture schema.

import org.nuxeo.common.utils.FileUtils;
import org.nuxeo.ecm.core.api.Blob;
import org.nuxeo.ecm.core.api.CoreSession;
import org.nuxeo.ecm.core.api.DocumentModel;
import org.nuxeo.ecm.core.api.impl.blob.FileBlob;
import org.nuxeo.ecm.platform.picture.api.PictureView;
import org.nuxeo.ecm.platform.picture.api.adapters.MultiviewPicture;
import org.nuxeo.ecm.platform.types.adapter.TypeInfo;

public class ThumbnailPictureFactory implements ThumbnailFactory {
    @Override
    public Blob getThumbnail(DocumentModel doc, CoreSession session)
            throws ClientException {
        if (!doc.hasFacet("Picture")) {
            throw new ClientException("Document is not a picture");
        }
        // Choose the nuxeo default thumbnail of the picture views if exists
        MultiviewPicture mViewPicture = doc.getAdapter(MultiviewPicture.class);
        PictureView thumbnailView = mViewPicture.getView("Small");
        if (thumbnailView == null || thumbnailView.getBlob() == null) {
            // try thumbnail view
            thumbnailView = mViewPicture.getView("Thumbnail");
            if (thumbnailView == null || thumbnailView.getBlob() == null) {
                TypeInfo docType = doc.getAdapter(TypeInfo.class);
                return new FileBlob(
                        FileUtils.getResourceFileFromContext("nuxeo.war"
                                + File.separator + docType.getBigIcon()));
            }
        }
        return thumbnailView.getBlob();
    }
    @Override
    public Blob computeThumbnail(DocumentModel doc, CoreSession session) {
        return null;
    }
}

And then the usage of ThumbnailAdapter:

import org.nuxeo.common.utils.FileUtils;
import org.nuxeo.ecm.core.api.Blob;
import org.nuxeo.ecm.core.api.CoreSession;
import org.nuxeo.ecm.core.api.DocumentModel;
import org.nuxeo.ecm.core.api.blobholder.BlobHolder;
import org.nuxeo.ecm.core.api.impl.DocumentModelImpl;
import org.nuxeo.ecm.core.api.impl.blob.FileBlob;
import org.nuxeo.ecm.core.api.thumbnail.ThumbnailAdapter;
import org.nuxeo.ecm.core.api.CoreSession;
import com.google.inject.Inject;

@Inject
CoreSession session;

// Create a picture
DocumentModel root = session.getRootDocument();
DocumentModel picture = new DocumentModelImpl(root.getPathAsString(),"pic", "Picture");
picture.setPropertyValue("picture:views", (Serializable) createViews());
picture = session.createDocument(picture);
session.save();

// Using BlobHolder adapter
BlobHolder bh = picture.getAdapter(BlobHolder.class);
Blob blob = new FileBlob(getFileFromPath("test-data/big_nuxeo_logo.gif"), "image/gif",null, "big_nuxeo_logo.gif", null);
bh.setBlob(blob);
session.saveDocument(picture);
session.save();

// Using ThumbnailAdapter
ThumbnailAdapter pictureThumbnail = picture.getAdapter(ThumbnailAdapter.class);
Blob thumbnail = pictureThumbnail.getThumbnail(session);
String fileName = thumbnail.getFilename();

Default Nuxeo thumbnail factories fall back on Nuxeo Document BigIcon if no thumbnail has been found.

Here is a way to find it properly:

Blob getDefaultThumbnail(DocumentModel doc) {
    if (doc == null) {
        return null;
    }
    TypeInfo docType = doc.getAdapter(TypeInfo.class);
    String iconPath = docType.getBigIcon();
    if (iconPath == null) {
        iconPath = docType.getIcon();
    }
    if (iconPath == null) {
        return null;
    }

    try {
        File iconFile = FileUtils.getResourceFileFromContext("nuxeo.war" + File.separator + iconPath);
        if (iconFile.exists()) {
            MimetypeRegistry mimetypeRegistry = Framework.getService(MimetypeRegistry.class);
            String mimeType = mimetypeRegistry.getMimetypeFromFile(iconFile);
            if (mimeType == null) {
                mimeType = mimetypeRegistry.getMimetypeFromFilename(iconPath);
            }
            return Blobs.createBlob(iconFile, mimeType);
        }
    } catch (IOException e) {
        log.warn(String.format("Could not fetch the thumbnail blob from icon path '%s'", iconPath), e);
    }

    return null;
}

Thumbnail Architecture

Here, we can see the ThumbnailAdapter to use and factories like the default one ThumbnailDocumentFactory and ThumbnailPictureFactory: