Nuxeo Platform 7.2 which was released last week introduced some new features and enhancements that everyone will love, especially developers! These enhancements include new capabilities to define and publish content. I wanted to talk about some of the new features I have been working on:

  • Data validation
  • References validation
  • Pluggable JSON

This blog is the first in a series that will allow us to approach each new feature by creating a simple application. In this blog, we will learn about sharing pictures from an e-commerce website through the Nuxeo Platform.

The “Collaborative Showroom” Application

The main purpose of this application will be to collect contents from the customers of an existing e-commerce website and publish them. Our example is an online kitchen reseller who wants to provide some kitchen installation examples to their customers.

Collaborative Showrooms for an E-commerce WebsiteCollaborative Showrooms for an E-commerce Website

The source code of this application is available on github.

$ git clone https://github.com/nuxeo-sandbox/nuxeo-demo-collaborative-showroom.git $ cd nuxeo-demo-collaborative-showroom $ mvn clean install $ cp target/*.jar path/to/nuxeo/nxserver/bundles/ => Start Nuxeo and go to http://localhost:8080/nuxeo/site/showroom/products

To understand how it works, start with the MANIFEST.MF file and the corresponding contribs.

The Problem is We Have to Validate

In this ecommerce website, we have to upload a picture and create a document which contains the metadata related to the uploaded picture (related kitchen, title, geolocation of the customer, category, etc.). Since most of the data comes from Internet customers, we’d like validate the data. The Nuxeo Platform and the website are two different systems in our example. So, we’d like validate that the pictures shared on the platform are related to a real kitchen published on the website.

Learn more about ecommerce merchandising best practices in our whitepaper.

Sharing pictures - Logical architectureSharing pictures - Logical architecture

Our Solution Approach

Document Management is a native component of the Nuxeo Platform. It provides several channels to create documents. In our case, we’ll use the Nuxeo REST API endpoints to upload the picture and create the document. With the 7.2 Fast Track release, the Nuxeo Platform provides new features to manage validation (here is the documentation). The validation is activated by default. We just have to provides the validation rules. There are 3 parts which must be validated:

  • The category must refer to a real “subject” of dublincore. And the nature must refer to a real “nature” of dublincore (usefull for classification).
  • The customer geolocation is not mandatory but if it’s present, it must contain a valid latitude and longitude.
  • The kitchen reference is mandatory and must refer to a real kitchen of the website.

Once this rules are described our work will be done. The Nuxeo Platform will validate the document while creating it.

Validating the Geolocation

For the geolocation, we have to provide a custom schema which will contain two fields: latitude and longitude. The Nuxeo Platform now supports most of XSD constraints. We can use them to describe the valid ranges for our fields. A valid latitude is a number between -90.0 and +90.0. Let’s have a look on the latitude definition.

<xs:element name="latitude">
   <xs:simpleType>
      <xs:restriction base="xs:double">
         <xs:minInclusive value="-90.0" />
         <xs:maxInclusive value="90.0" />
      </xs:restriction>
    </xs:simpleType>
</xs:element>

We just have to do the same things for the “longitude” field. -180.0 <=longitude <= 180.0

Now any channel to create documents will throw an exception if it contains an invalid longitude or an invalid latitude. The REST API will return a “400 Bad Request” response which contains the broken rules described using JSON. From the customer’s browser, we’ll get the geolocation coordinates through the HTML5 API.

navigator.geolocation.getCurrentPosition(function(position) {
   var latitude = position.coords.latitude;
   var longitude = position.coords.longitude;
   // ...
});

Validating the Subject and the Nature

Starting from Nuxeo 7.2, the “dublincore” schema contains specific definitions for some fields:

  • The “creator” is defined as a valid Nuxeo user.
  • The “lastContributor” is defined as a valid Nuxeo user.
  • The “contributors” contains valid Nuxeo users.
  • The “subject” is defined as a valid entry of the “l10nsubjects” directory.
  • The “nature” is defined as a valid entry of the “nature” directory.
  • The “coverage” is defined as a valid entry of the “l10ncoverage” directory.

These definitions are based on the concept of ObjectResolver, which is able to validate whether a reference matches an object. The Nuxeo Platform provides base resolvers to make references to other documents, directory entries, users or groups.

<xs:element name="creator">
  <xs:simpleType>
    <xs:restriction
      base="xs:string"
      ref:resolver="userManagerResolver"
      ref:type="user" />
  </xs:simpleType>
</xs:element>

<xs:element name="nature">
  <xs:simpleType>
    <xs:restriction
      base="xs:string"
      ref:resolver="directoryResolver"
      ref:directory="nature" />
  </xs:simpleType>
</xs:element>

If some call to the REST API is done with invalid values, it will also throw an exception the same way as for the geolocation.

From the customer’s browser, we’ll choose static values to classify our document:

  • dc:subjects = “art/photography”
  • nature = “article”

Validating the Kitchen’s Reference

This is the most complex part. The Nuxeo Platform knows what’s a valid reference to a document, a user, a group or a directory’s entry. But it doesn’t know what’s a valid kitchen’s reference. We have provided the required logic.

First, we have to create a service client which gets products from the website. We also have to create a data-class to deliver the product’s data. This part of the implementation isn’t detailed here because of the specificity of each case. In our case, we are using a mock implementation:

Then, we have to create the “productReference” schema with a “product” field. It will contain the reference to the kitchen.

<xs:element name=_"product" type="xs:integer" /_>

We’d like to define it as a valid reference to a kitchen. The Nuxeo Platform provides an extension point to register a custom resolver. We have to provide an object implementing the ObjectResolver interface. That will allow us to define an object which makes a call to the ProductService and tries to get a kitchen which matches the given reference.

Let’s have a look at the implementation of the main methods: (the complete source code is here)

<small>public class ProductResolver implements ObjectResolver {

    // this method provides the Java type
    // which matches the references
    public List<Class<?>> getManagedClasses() {
        return Arrays.asList(Product.class);
    }

    // this method validates the given reference
    // true if it exists
    // false otherwise
    public boolean validate(Object value) {
        return fetch(value) != null;
    }

    // this method returns the Product with the given reference
    // null if it doesn't exists
    public Object fetch(Object value) {
        ProductService service =
                       Framework.getService(ProductService.class);
        if (value != null && value instanceof Number) {
            int reference = ((Number) value).intValue();
            return service.getProduct(reference);
        }
        return null;
    }

    // this method returns the reference of the given Product
    public Serializable getReference(Object object) {
        if (object != null && object instanceof Product) {
            return ((Product) object).getReference();
        }
        return null;
    }
}</small>

This is almost done, we just have to register our resolver and upgrade the “product” field definition.

<small><component name="org.nuxeo.demo.productResolver">
  <extension
    target="org.nuxeo.ecm.core.schema.ObjectResolverService"
    point="resolvers">
    <resolver
      type="productResolver"
      class="org.nuxeo.demo.ProductResolver" />
  </extension>
</component></small>

The “product” field becomes:

<xs:element name=_"product"_>
   <xs:simpleType>
      <xs:restriction
          base=_"xs:integer"
_          ref:resolver=_"productResolver"_ />
   </xs:simpleType>
</xs:element>

To make it mandatory, just add the nillable attribute: (see the final schema here)

<xs:element name=_"product"
_            nillable="false"
            nxsv:nillable="false">
   <xs:simpleType>
      <xs:restriction
          base=_"xs:integer"
_          ref:resolver=_"productResolver"_ />
   </xs:simpleType>
</xs:element>

Uploading the File

This has already been described in a previous post. It requires a first call to the REST API to store the file temporary. A second call creates the document and makes a reference to the uploaded file.

REST API - Batch File Upload sequenceREST API - Batch File Upload sequence

Integration with the Website

Here is the jQuery code to upload the picture:

<small>// get the customer's geolocation
var latitude = null, longitude = null;
navigator.geolocation.getCurrentPosition(function(position) {
  latitude = position.coords.latitude;
  longitude = position.coords.longitude;
});

// the file to upload
var file = $('input[type=file]#demo')[0].files[0];
var formData = new FormData();
formData.append("file", file);

// upload the file
$.ajax({
  type: 'POST', url: '/nuxeo/api/v1/automation/batch/upload',
  headers: {
    "X-Batch-Id": "PictureForKitchen10000",
    "X-File-Idx": "0"
  },
  data: formData,
  cache: false, contentType: false, processData: false,
  success: function(data) {
    // create the document
    $.ajax({
      type: 'POST',
      url: '/nuxeo/api/v1/path/default-domain/workspaces/showrooms/',
      contentType: "application/json", dataType: 'json',
      data: JSON.stringify({
          "entity-type":"document",
          "name":"picture-for-kitchen-10000",
          "type":"ShowroomEntry",
          "properties": {
          "dc:title":"Feedback for product #10000",
          "dc:nature":"article",
          "dc:subjects":["art/photography"],
          "loc:latitude":latitude,
          "loc:longitude":longitude,
          "pdt:product":10000,
          "file:content":{
            "upload-batch":"PictureForKitchen10000",
            "upload-fileId":"0"
          }
        }
      }),
      success: function (data) {
        alert('Your picture has been shared, thanks!')
      }
    });
  }
});</small>

Conclusion and Next Steps

The Nuxeo Platform is now able to automatically validate your data – even reference to content located in your other information systems. The showroom’s pictures are now collected from our customers - with valid content and stored in the Nuxeo Platform! Nuxeo Studio can easily provide complex workflows and processes in order to manage the large amount of content. In our case, it would probably be necessary to setup a workflow to validate the pictures (quality and reliability). We could send an email to our customer to tell him that the picture is published. We could also add a custom operation to give him a discount.

In the next post, I will show the publication of shared pictures. We will see how to effectively use the JSON API and also discover how to contribute to JSON marshalling. We will address the JSON conversion of the kitchen and its use in the existing API.

More Resources

Blog post: Batch Upload and document’s creation Source code: Collaborative Showroom Application source code Documentation: Field Constraints and Validation Documentation: How to Customize Document Validation