In this tutorial you create an app that operates on data stored outside the JCR repository. The app uses the content app framework but stores data on the local file system. This is an example of connecting to a custom data source. Editors don't have to leave Magnolia in order to work on third-party data.

Background

The content app framework allows you to create standardized apps. Standardization makes apps easy to use and learn. Since all content apps look and behave the same way, a user who has used one can quickly learn another, even if the apps operate on different content.

The data that content apps operate on is typically stored in the Java Content Repository (JCR). However, apps can also operate on data that resides outside the JCR. Editing and viewing custom data is a common requirement. For example, editors may need to access product data in a remote ERP system in order to create content to sell the products. Such remote data can reside in a relational database, on the filesystem, in a Web service, or some other data delivery service. For convenience, you want to make the remote data available inside Magnolia in a familiar environment so editors don't need to jump from one system to another. 

Read  My first content app  and the content app framework if you are not yet familiar with JCR-based content apps. In this tutorial you customize an app which requires some Java programming. You need a development environment and a basic understanding of Magnolia modules. You will work with interfaces originating from the Vaadin framework used to build the Magnolia UI. It helps if you already know Google Web Toolkit (GWT).

Create a module

The file system browser app created in this tutorial needs to be deployed as a Magnolia module. Choose from the following options depending on your skill level.

Option 1: Clone the project in Git

Choose this option if you know how to work with a Magnolia project and Git. You get the module code on your local system and can edit it in your IDE.

  1. Clone the fs-browser-app repository.

    git clone http://git.magnolia-cms.com/git/documentation/fs-browser-app.git

    If you are using Magnolia version 5.5 or 5.4 - check out the branch branch-1.0.x of this module. (The master branch is optimized for Magnolia 5.6 version and higher.)

    git checkout branch-1.0.x
  2. Import the project into your IDE. Here's what the project looks like in IntelliJ IDEA:
  3. Build the project into a JAR and deploy it to Magnolia instance or run the project in your IDE. To run it in the IDE, add the module as a dependency in the .pom file of your bundle, see below.

Option 2: Add the project as a dependency to your bundle

Choose this option if you want to add the project to your own Magnolia bundle. The module code will not be stored on your local system. Add the following dependency to your bundle:

Error rendering macro 'artifact-maven-dependencies-snippet-macro'

com.sun.jersey.api.client.ClientHandlerException: java.net.NoRouteToHostException: No route to host (Host unreachable)

Option 3: Download the module JAR

Choose this option if you are new to Magnolia or don't have a development environment. You get the complete app and can use it.

  1. Download the

    Error rendering macro 'artifact-resource-macro'

    com.sun.jersey.api.client.ClientHandlerException: java.net.NoRouteToHostException: No route to host (Host unreachable)

     JAR from Nexus and follow the standard module installation instructions.
  2. Copy the JAR into <CATALINA_HOME>/webapps/<contextPath>/WEB-INF/lib folder. Typically this is <CATALINA_HOME>/webapps/magnoliaAuthor/WEB-INF/lib.
  3. Restart your Magnolia instance and run the Web update. This will install the module.

Overview

To create a custom content app you need to implement:

  • Container and related classes.
  • Custom presenter classes for every view type you want (ListPresenter, TreePresenter, ThumbnailPresenter, others)
  • Custom ContentConnector
  • Action classes to interact with and manipulate your custom content
  • Custom AvailabilityRule classes (if required)
  • Configuration

Creating a container

A container represents your data source in Vaadin context. The container provides methods to get, add and remove items and properties in the data source. The container can hold 0 to n items. It might help to first understand Vaadin's concept of an item, see com.vaadin.data.Item. See also the com.vaadin.data.Property interface. Item and Property are important and used heavily in containers.

The interface you have to implement for your container is com.vaadin.data.Container. If you want to have a more sophisticated container you also may want to implement:

  • info.magnolia.ui.workbench.container.Refreshable
  • com.vaadin.data.Container.Sortable
  • com.vaadin.data.Container.Indexed

The Vaadin framework itself provides some container implementations which may be helpful for your own custom container. Use them as a source of inspiration or just as a superclass:

  • com.vaadin.data.util.BeanContainer
  • com.vaadin.data.util.sqlcontainer.SQLContainer
  • com.vaadin.ui.Table
  • com.vaadin.ui.TreeTable

File system container

The container class for the file system browser app is info.magnolia.filesystembrowser.app.contentview.RefreshableFSContainer. The class delegates most of the work to com.vaadin.data.util.FilesystemContainer which again is a container implementation provided by the Vaadin framework. When creating your own custom container, study the interface and related classes.

Content views and presenters

Workbench is a view that displays a list of content items in a workspace. It is part of the content app framework, typically defined in the browser subapp. The workbench contains at list of content views. Typical view types are tree, list and thumbnail. The workbench also provides a search box; results from search are displayed in the search view.

Your custom app can use the same views as a typical JCR content app such as tree, list and thumbnail but other views are possible too.

To implement a presenter:

  1. Provide a concrete implementation of info.magnolia.ui.workbench.AbstractContentPresenterBase.

  2. Implement the initializeContainer() method. This method is a link to your custom container. Instantiate and initialize the container in the method.

    protected abstract Container initializeContainer();
  3. Depending on whether you want your custom content presenter to display a tree, list or thumbnail view, extend one of the following:

    • info.magnolia.ui.workbench.list.ListPresenter

    • info.magnolia.ui.workbench.tree.TreePresenter

    • info.magnolia.ui.workbench.thumbnail.ThumbnailPresenter

  4. Override the initializeContainer() method.

In the example app we extend the TreePresenter:

info.magnolia.filesystembrowser.app.contentview.FSTreePresenter
public class FSTreePresenter extends TreePresenter {

    @Inject
    public FSTreePresenter(TreeView view, ComponentProvider componentProvider) {
        super(view, componentProvider);
    }

    @Override
    public Container initializeContainer() {
        return new RefreshableFSContainer(new File(((FileSystemContentConnector)contentConnector).getRootFolder()));
    }
}

The code fragment above is simplified. The package declaration, imports and some comments have been removed.

The other presenter used for the fs-browser-app is very similar; it extends info.magnolia.ui.workbench.thumbnail.ThumbnailPresenter, but the method initializeContainer() is a little bit more complicated, but still easy.

When you have the custom-presenters, you also should create definition classes:

  • info.magnolia.filesystembrowser.app.contentview.FSTreePresenterDefinition
  • info.magnolia.filesystembrowser.app.contentview.FSThumbnailPresenterDefinition

for example:

info.magnolia.filesystembrowser.app.contentview.FSThumbnailPresenterDefinition
public class FSThumbnailPresenterDefinition extends ThumbnailPresenterDefinition {

    public FSThumbnailPresenterDefinition() {
        setImplementationClass(FSThumbnailPresenter.class);
    }
}

Now the views can be configured:

Node nameValue
 
workbench

 
contentViews


 
tree


 
columns


 
name


 
class

info.magnolia.ui.workbench.column.definition.PropertyColumnDefinition

 
editable

true

 
expandRatio

2

 
propertyName

Name

 
class

info.magnolia.filesystembrowser.app.contentview.FSTreePresenterDefinition

 
thumbnail


 
class

info.magnolia.filesystembrowser.app.contentview.FSThumbnailPresenterDefinition

The tree above is just a part of the whole configuration of the fs-browser-app. However, it contains the relevant parts for the content views and presenters.

Creating a custom content connector

The content connector is one of the core pieces for the your custom content app with non-JCR content. You need a custom implementation and contentConnector node in the configuration.

In the fs-browser-app, the custom implementation is info.magnolia.filesystembrowser.app.contentconnector.FileSystemContentConnector. Its definition class is info.magnolia.filesystembrowser.app.contentconnector.FSContentConnectorDefinition.

Node nameValue

 
browser


 
contentConnector


 
class

info.magnolia.filesystembrowser.app.contentconnector.FSContentConnectorDefinition

 
rootFolder

/Users/cmeier/Documents/magnolia

The contentConnector node of your own custom content app can have more properties. It depends on the implementation of your definition class. Here FSContentConnectorDefinition has just one bean-property: the root folder. The other property class comes from the superclass of the definition class (info.magnolia.ui.vaadin.integration.contentconnector.ConfiguredContentConnectorDefinition). You must always provide the property class when creating a custom content app.

Implement action classes to operate on custom content

Now that you have a container and a content connector, your data-source is anchored in the content-app and is now ready to be used. As you already know from JCR-related content apps, to work with the items you have to create actions, action definitions and you have to configure the app with the available actions. This is probably now a good moment to update your knowledge concerning Dialog action definition and Action bar definition.

Start with info.magnolia.filesystembrowser.app.action.RefreshFSAction:

info.magnolia.filesystembrowser.app.action.RefreshFSAction
public class RefreshFSAction<T extends RefreshFSActionDefinition> extends AbstractAction<T> {
    private ContentConnector contentConnector;
    private EventBus eventBus;
    @Inject
    public RefreshFSAction(T definition, ContentConnector contentConnector, @Named(AdmincentralEventBus.NAME)EventBus eventBus) {
        super(definition);
        this.contentConnector = contentConnector;
        this.eventBus = eventBus;
    }
    @Override
    public void execute() throws ActionExecutionException {
        eventBus.fireEvent(new ContentChangedEvent(contentConnector.getDefaultItemId()));
    }
}

This action does not do a lot. The execute method is only firing an event which leads to a refresh of the content view. Imagine that you have added a file to a root folder which is represented by your fs-browser-app and you want your app to reflect the changes in the filesystem. You can inject the contentConnector into the constructor which is really handy.

The next example shows the typical pattern where the scenario is started with OpenEditDialogAction and where a dialog must be provided. In that case the custom action info.magnolia.filesystembrowser.app.action.SaveFileActionDefinition is defined as the commit action in the dialog.

Node nameValue

 
browser


 
actions


 
editFile


 
class

info.magnolia.ui.framework.action.OpenEditDialogActionDefinition

 
icon

icon-edit

 
dialogName

fs-browser-app:editFileProperties

 
subAppId

detail
Node nameValue

 
dialogs


 
editFileProperties


 
editFile


 
form


 
action


 
commit


 
class

info.magnolia.filesystembrowser.app.action.SaveFileActionDefinition

In your custom conten-app you would have more sophisticated action - e.g. you could implement actions to create, update and delete (CRUD) items.

Implement custom availability rules

Availability rules are optional. Generally, availability classes implement info.magnolia.ui.api.availability.AvailabilityRule, most of them by extending info.magnolia.ui.api.availability.AbstractAvailabilityRule.

info.magnolia.ui.api.availability.AbstractAvailabilityRule
public abstract class AbstractAvailabilityRule implements AvailabilityRule {
    @Override
    public boolean isAvailable(Collection<?> itemIds) {
        if (itemIds == null || itemIds.size() == 0) {
            return false;
        }
        // for selected items
        for (Object item : itemIds) {
            if (!isAvailableForItem(item)) {
                return false;
            }
        }
        return true;
    }
    protected abstract boolean isAvailableForItem(Object itemId);
}

Note, that the older version was depending on javax.jcr.Item to compare, whereas the newer version is less strict typed - which allows it to implement availability rule classes for non-JCR related content. The fs-browser-app has such an availability rule class, too:

info.magnolia.filesystembrowser.app.action.availability.IsImageRule
public class IsImageRule extends AbstractAvailabilityRule {
    @Override
    protected boolean isAvailableForItem(Object itemId) {
        if (itemId instanceof File) {
            String mimeType = FileTypeResolver.getMIMEType((File) itemId);
            return (mimeType != null &amp;&amp; mimeType.matches("image.*"));
        }
        return false;
    }
}

The rule class itself has a definition class - info.magnolia.filesystembrowser.app.action.availability.IsImageRuleDefinition - which then is used in the configuration:

Node nameValue

 
actions


 
editImage


 
availability


 
rules


 
image


 
class

info.magnolia.filesystembrowser.app.action.availability.IsImageRuleDefinition

 
anotherRule


 
class

com.yours.yourcustomapp.app.action.availability.AnotherRuleDefinition

 
againAnotherRule


 
implementationClass

com.yours.yourcustomapp.app.action.availability.AgainAnotherRule

In the above tree you can see that it is possible to combine more than one availability rule classes. The 2nd and the 3rd one under the node filename doesn't exist and is just an example. When using more then one class, their results are combined with AND-operator.

Instead of using the class-property, which points to a definition-class, you also can use the implementationClass-property, which then must point directly to the rule-implementation-class,

Finally assembly

Now all parts are ready and if not already done - the app can be configured. If you are unsure about how to do that, have a look at Configuration. Some of the details of the configuration have been shown above. To finish, have a look at the screenshot of the configuration of the fs-browser-ap:



#trackbackRdf ($trackbackUtils.getContentIdentifier($page) $page.title $trackbackUtils.getPingUrl($page))