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 service
The service that handles thumbnail factories contributions.
- Interface:
ThumbnailService
- Implementation:
ThumbnailServiceImpl
- Component:
org.nuxeo.ecm.core.api.thumbnail.ThumbnailService
- Extension point:
thumbnailFactory
- Interface:
Default Thumbnail factories
ThumbnailDocumentFactory
: Default thumbnail factory for all non-folderish documents. Returns the main blob converted in thumbnail or get the document's big icon as a thumbnail.ThumbnailPictureFactory
: Picture thumbnail factory from DAM.ThumbnailVideoFactory
: Video thumbnail factory from DAM.ThumbnailAudioFactory
: Audio thumbnail factory from DAM.
Thumbnail listeners
UpdateThumbnailListener
: Thumbnail listener handling creation and update of document event to store document thumbnail preview (only for the File document type).CheckBlobUpdateListener
: Thumbnail listener handling document blob update and checking changes. Fires ascheduleThumbnailUpdate
event if it's the case that will triggerUpdateThumbnailListener
.
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 (ignoringfacet
) - Then, if the document has the
facet
, the factory is used. - If the document does not match the
docType
and does not have thefacet
, 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
: