Nuxeo/Blogs

Product & Development / All about the Nuxeo Platform, from strategy to feature highlights to dev tricks

[Monday Dev Heaven] Automatic document creation in Nuxeo, Part 2

with one comment

As I was working on the content template service for my last blog, I saw some things that neeeded to be done. There is an ImportBasedFactory hanging around since basically the beginning of the ContentTemplateService. It has never been implemented. Here’s how we’re gonna do it.

The ImportFactory

The goal of this factory will be to import some content from a file or a folder instead of a templateItem like we saw last week.

To create a new factory, I need a new class that implements the ContentFactory interface. A good thing to know is that there is an abstract class BaseContentFactory that implements the interface and gives a method to handle the CoreSession. So right now the ImportBasedFactory looks like this:

public class ImportBasedFactory extends BaseContentFactory {

    public void createContentStructure(DocumentModel eventDoc)
            throws ClientException {
        throw new UnsupportedOperationException();
    }

    public boolean initFactory(Map<String, String> options,
            List<ACEDescriptor> rootAcl, List<TemplateItemDescriptor> template) {
        throw new UnsupportedOperationException();
    }

}

We have two methods to implement. initFactory will be used to retrieve the path of the zip file from the options map. createContentStructure will be used to import the documents from the file.

Retrieve the file

I’m going to use the option map to get the file. I have many options. I can retrieve the file from the bundle resources, from Nuxeo’s data folder or from any path on the server. To represent these three options I could have a contribution like this:

  <extension
      target="org.nuxeo.ecm.platform.content.template.service.ContentTemplateService"
      point="factoryBinding">

    <factoryBinding name="RootFactory" factoryName="SimpleTemplateFactory" targetType="Root">
      <option name="path">resource:domain.zip</option>
      <option name="overwrite">false</option>
    </factoryBinding>
  </extension>

My initFactory method will simply have to store the options on our factory. I will only use two options here: path and overwrite. path will be use to retrieve the file to import. I plan to have an address in one of these formats – resource:domain.zip or absolute:/home/nuxeo/zip/domain.zip or even nxData:domain.zip. The absolute path will reference a file on the server’s filesystem, nxData will reference a file inside the Nuxeo data folder and resource will reference a file in the bundle’s resources. I have used a Java enum for this. It has 3 different values: absolute, nxData and resource. I declare an abstract method getFile in the enum. This way I can implement a different getFile for each value. Then all I have to do is add a static method like getResource which takes a single String parameter. This parameter is split at the first ‘:’ char. This way, I can get the value using enum.valueOf and call its getFile method. If this file does not exist, initFactory will return false and the factory won’t be executed.

public class ImportBasedFactory extends BaseContentFactory {

    private static final Log log = LogFactory.getLog(ImportBasedFactory.class);

    public static final String IMPORT_FILE_PATH_OPTION = "path";

    public static final String IMPORT_OVERWRITE_OPTION = "overwrite";

    protected FileManager fileManager;

    protected Map<String, String> options;

    protected File importedFile;

    protected Boolean overwrite = false;

    public enum PathOptions {
        resource {
            @Override
            public File getFile(String path) {
                return FileUtils.getResourceFileFromContext(path);
            }
        },
        nxData {
            @Override
            public File getFile(String path) {
                File nxDdataFolder = Environment.getDefault().getData();
                return new File(nxDdataFolder, path);
            }
        },
        absolute {
            @Override
            public File getFile(String path) {
                return new File(path);
            }
        };

        protected abstract File getFile(String path);

        public static File getResource(String path) {
            String[] s = path.split(":", 2);
            String resourceType = s[0];
            String resourcePath = s[1];
            PathOptions option = valueOf(resourceType);
            if (option == null) {
                log.error("Unsupported resource type: " + resourceType);
                return null;
            } else {
                return option.getFile(resourcePath);
            }
        }
    }

    @Override
    public boolean initFactory(Map<String, String> options,
            List<ACEDescriptor> rootAcl, List<TemplateItemDescriptor> template) {
        this.options = options;
        overwrite = Boolean.valueOf(options.get(IMPORT_OVERWRITE_OPTION));
        String path = options.get(IMPORT_FILE_PATH_OPTION);
        File file = PathOptions.getResource(path);
        if (file != null) {
            if (file.exists()) {
                importedFile = file;
                return true;
            } else {
                log.warn("Following file does not exist: "
                        + file.getAbsolutePath());
            }
        }
        return false;
    }

Creating the content

The createContentStructure will use the FileManager service to import the file. The first thing I do is call initSession from the parent Class to make sure I have an open coreSession. Then check if the created document is a version or not. If not, I call the importBlob method. This method uses the fileManager to import files as documents. The first thing I do is check if the file is a directory. If it is, then I run importBlob for its child files. If not, I use the simple FileManagerService.createDocumentFromBlob method. This way I can import anything from a folder and its contents to a zip XML export from Nuxeo to a simple file like a picture or a pdf. It’s then really easy to customize if you add different import plugins.

    @Override
    public void createContentStructure(DocumentModel eventDoc)
            throws ClientException {
        initSession(eventDoc);

        if (eventDoc.isVersion()) {
            return;
        }
        try {
            String parentPath = eventDoc.getPathAsString();
            importBlob(importedFile, parentPath);
        } catch (Exception e) {
            throw new RuntimeException(e);
        }

    }

    /**
     * Use fileManager to import the given file.
     *
     * @param file to import
     * @param parentPath of the targetDocument
     * @throws Exception
     */
    protected void importBlob(File file, String parentPath) throws Exception {
        if (file.isDirectory()) {
            DocumentModel createdFolder = getFileManagerService().createFolder(
                    session, file.getAbsolutePath(), parentPath);
            File[] files = file.listFiles();
            for (File childFile : files) {
                importBlob(childFile, createdFolder.getPathAsString());
            }
        } else {
            Blob fb = new FileBlob(file);
            fb.setFilename(file.getName());
            getFileManagerService().createDocumentFromBlob(session, fb,
                    parentPath, overwrite, fb.getFilename());
        }
    }

    protected FileManager getFileManagerService() {
        if (fileManager == null) {
            try {
                fileManager = Framework.getService(FileManager.class);
            } catch (Exception e) {
                throw new RuntimeException(
                        "Unable to get FileManager service ", e);
            }
        }
        return fileManager;
    }
}

That’s of course a first step of improvement. Making this factory available outside a factoryBinding could be another.
That’s it for today. See ya Friday!

April 2nd, 2012 at 7:57 pm

About Laurent Doguin

Laurent works as developer and community liaison at Nuxeo, a software company providing a full Enterprise Content Management Platform, open source, for any kind of content-driven application.