As we've seen last week in the Extend Nuxeo Drive series, files and folders are represented server side using adapters. By default it's the DocumentBackedFileItem adapter that is used for simple, non-folderish documents. When you get the file from a document, the document BlobHolder is used. It's a document adapter used to get or set the main file of a document. It means that the file you see on the desktop while using Drive has been retrived with this code:

BlobHolder bh = documentModel.getAdapter(BlobHolder.class);
Blob b = bh.getBlob();

It also means that when you modify a file on the desktop, it's updated to the document using the BlobHolder.setBlob method. Once you know that, you can do some interesting stuff without having to write Nuxeo Drive factories or adapters (sorry, not yet, maybe in the next post :-) ).

Here's a fairly simple example. The book document type available in the BookTraining-Day3 studio template has no binary field, only metadata. So we have to think of a specific blob holder. We can easily represent it as a CSV file like this:


"Name","Value"
"Author","Soulcie"
"Borrowed By","Administrator"
"Category","comics/manga/managa1;comics/manga;"
"ISBN","1307483092"
"Publication date","5/23/13 12:00 AM"
"Rating","5"

Now imagine that when the user modifies some of the values, we update the document's metadata. This will happen in the BlobHolder.setBlob method. Here's a simple implementation of a BookBlobHolder:

package org.nuxeo.sample;

import java.io.IOException;
import java.io.Serializable;
import java.io.StringWriter;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Calendar;
import java.util.List;

import org.nuxeo.ecm.core.api.Blob;
import org.nuxeo.ecm.core.api.ClientException;
import org.nuxeo.ecm.core.api.DocumentModel;
import org.nuxeo.ecm.core.api.blobholder.DocumentBlobHolder;
import org.nuxeo.ecm.core.api.impl.blob.StringBlob;
import org.nuxeo.ecm.core.api.model.PropertyException;
import org.nuxeo.ecm.core.schema.DocumentType;
import org.nuxeo.ecm.core.schema.SchemaManager;
import org.nuxeo.ecm.core.schema.types.Field;
import org.nuxeo.ecm.core.schema.types.ListType;
import org.nuxeo.ecm.core.schema.types.Type;
import org.nuxeo.ecm.core.schema.types.primitives.BooleanType;
import org.nuxeo.ecm.core.schema.types.primitives.DateType;
import org.nuxeo.ecm.core.schema.types.primitives.DoubleType;
import org.nuxeo.ecm.core.schema.types.primitives.IntegerType;
import org.nuxeo.ecm.core.schema.types.primitives.LongType;
import org.nuxeo.ecm.core.schema.types.primitives.StringType;
import org.nuxeo.runtime.api.Framework;

import au.com.bytecode.opencsv.CSVReader;
import au.com.bytecode.opencsv.CSVWriter;

public class BookBlobHolder extends DocumentBlobHolder {

public static final String[] HEADERS = { "Name", "Value" };

public enum BookProperties {
AUTHOR("Author", "bk:author"), BORROWED_BY("Borrowed By",
"bk:borrowedBy"), CATEGORY("Category", "bk:category"), ISBN(
"ISBN", "bk:isbn"), PUBLICATION_DATE("Publication date",
"bk:publicationDate"), RATING("Rating", "bk:rating");

private String propertyName;

private String name;

private BookProperties(String name, String propertyName) {
this.name = name;
this.propertyName = propertyName;
}

public void setProperty(DocumentModel doc, String stringValue)
throws PropertyException, ClientException {
DocumentType docType = Framework.getLocalService(
SchemaManager.class).getDocumentType(doc.getType());
Serializable value = convertStringValue(docType, propertyName,
stringValue);
doc.setPropertyValue(propertyName, value);
}

public String getPropertyValue(DocumentModel doc)
throws PropertyException, ClientException {
DocumentType docType = Framework.getLocalService(
SchemaManager.class).getDocumentType(doc.getType());
Serializable value = doc.getProperty(propertyName).getValue();
if (value == null) {
return "";
}
return convertValueToString(docType, propertyName, value);
}

public String[] getLine(DocumentModel doc) throws PropertyException,
ClientException {
String[] line = new String[2];
line[0] = name;
line[1] = getPropertyValue(doc);
return line;
}

public static BookProperties fromString(String name) {
if (name != null) {
for (BookProperties b : BookProperties.values()) {
if (name.equalsIgnoreCase(b.name)) {
return b;
}
}
}
return null;
}

protected Serializable convertStringValue(DocumentType docType,
String fieldName, String stringValue) {
if (docType.hasField(fieldName)) {
Field field = docType.getField(fieldName);
if (field != null) {
try {
Serializable fieldValue = null;
Type fieldType = field.getType();
if (fieldType.isListType()) {
Type listFieldType = ((ListType) fieldType).getFieldType();
if (listFieldType.isSimpleType()) {
fieldValue = stringValue.split(";");
} else {
fieldValue = (Serializable) Arrays.asList(stringValue.split(";"));
}
} else {
if (field.getType().isSimpleType()) {
if (field.getType() instanceof StringType) {
fieldValue = stringValue;
} else if (field.getType() instanceof IntegerType) {
fieldValue = Integer.parseInt(stringValue);
} else if (field.getType() instanceof LongType) {
fieldValue = Long.parseLong(stringValue);
} else if (field.getType() instanceof DoubleType) {
fieldValue = Double.parseDouble(stringValue);
} else if (field.getType() instanceof BooleanType) {
fieldValue = Boolean.valueOf(stringValue);
} else if (field.getType() instanceof DateType) {
fieldValue = SimpleDateFormat.getInstance().parse(
stringValue);
}
}
}
return fieldValue;
} catch (Exception e) {
throw new RuntimeException(e);
}
}
}
return null;
}

protected String convertValueToString(DocumentType docType,
String fieldName, Serializable value) {
if (docType.hasField(fieldName)) {
Field field = docType.getField(fieldName);
if (field != null) {
try {
String stringValue = null;
Type fieldType = field.getType();
if (fieldType.isListType()) {
Type listFieldType = ((ListType) fieldType).getFieldType();
if (listFieldType.isSimpleType()) {
String[] arrayValue = (String[]) value;
StringBuilder sb = new StringBuilder();
for (int i = 0; i < arrayValue.length; i++) {
sb.append(arrayValue[i]);
sb.append(";");
}
stringValue = sb.toString();
} else {
List<String> listValue = (List<String>) value;
StringBuilder sb = new StringBuilder();
for (String string : listValue) {

sb.append(string);
sb.append(";");
}
stringValue = sb.toString();
}
} else {
if (field.getType().isSimpleType()) {
if (field.getType() instanceof DateType) {
Calendar date = (Calendar) value;
stringValue = SimpleDateFormat.getInstance().format(
date.getTime());
} else {
stringValue = String.valueOf(value);
}
}
}
return stringValue;
} catch (Exception e) {
throw new RuntimeException(e);
}
}
}
return null;
}
}

public BookBlobHolder(DocumentModel doc, String path) {
super(doc, path);
}

@Override
public Blob getBlob() throws ClientException {
try {
StringWriter sw = new StringWriter();
CSVWriter csw = new CSVWriter(sw, ',', '"');
List<String[]> allLines = new ArrayList<String[]>();
allLines.add(HEADERS);
for (BookProperties bookProperty : BookProperties.values()) {
allLines.add(bookProperty.getLine(doc));
}
csw.writeAll(allLines);
csw.flush();
csw.close();
sw.close();
Blob b = new StringBlob(sw.toString(), "text/csv");
String filename = doc.getTitle() + ".csv";
b.setFilename(filename);
return b;
} catch (IOException e) {
throw new RuntimeException(e);
}
}

@Override
public void setBlob(Blob blob) throws ClientException {
if (blob == null) {
return;
}
CSVReader csvReader = null;
try {
csvReader = new CSVReader(blob.getReader(), ',', '"');
List<String[]> lines = csvReader.readAll();
for (String[] line : lines) {
String name = line[0];
BookProperties bookProperty = BookProperties.fromString(name);
if (bookProperty != null && line.length == 2) {
String value = line[1];
bookProperty.setProperty(doc, value);
}
}
csvReader.close();
} catch (IOException e) {
throw new RuntimeException(e);
}
}

@Override
public String getHash() throws ClientException {
return doc.getId() + getModificationDate().toString();
}

}


 

<?xml version="1.0"?>
<component name="org.nuxeo.sample.drive.bh.adapters">

<extension
target="org.nuxeo.ecm.core.api.blobholder.BlobHolderAdapterComponent"
point="BlobHolderFactory">
<blobHolderFactory name="bookBh" docType="Book"
class="org.nuxeo.sample.BookBlobHolderFactory" />
</extension>

</component>


There's a lot of boring code to get and set the values of the different properties used by the Book document type. All types are not supported, but it's a start. It handles all scalar properties and simple lists of scalar item (using ';' char as a separator). Now you will see all your Book documents as CSV files on the desktop. See, just with BlobHolder you can do some interesting things :)