Extend eXo Cloud Drive to new cloud services with the Connector API: Java API (Part 2 of 3)
This post is a continuation of an introduction to Cloud Drive add-on’s Connector API. This add-on lets you connect cloud files into eXo with just a click. We introduced an API to allow other developers to extend the add-on with the support of more cloud services.
In this post we will look at its major component: the Java API. In the previous blog post, we already reviewed the architecture and found where the Connector API allows you to extend the add-on. You also know about a template project that can help to bootstrap the development of a new connector.
At this point you already have a project for your connector, created from scratch or copied from the template, and we can start to code the connector plugin class—a programmatic entry point for the add-on.
Connector implementation
Each connector introduces a plugin extended from required methods for authorization, cloud provider, and drive creation and, finally, configure with this component plugin. It is the short story of connector creation. But let’s look deeper and understand its internal workings.
class. It already implements a component plugin interface and offers support for a connector configuration. You implementThe Cloud Drive add-on itself consists of a core and ECMS services. The core is responsible for the drive’s folder and files in JCR, their synchronization, and the storage of user credentials. The core also provides RESTful web services to connect, sync, and read the drive. The ECMS component is for user interface (UI) integrations in eXo. Connector API is split a similar way: core classes are mandatory, while REST services and UI are optional. Below is a brief overview of the core classes that Connector API requires. Web-services and UI will be discussed in a later, dedicated chapter.
Cloud Drive relies on OAuth2 for authorization and thus splits the process of granting permissions and actual drive connection. For this purpose a connector plugin should be able to provide an instance of CloudProvider for your cloud service during the server start (start of CloudDriveService). It should then be able to authenticate a user with an OAuth2 code (returned by the cloud login flow) and return CloudUser instance. The core add-on will use this user to initiate a drive creation in eXo. For this purpose your connector will create CloudDrive from an existing user and given JCR node (a root folder for the drive). This is how new drives are created by Cloud Drive. But your connector should also be able to load already connected drives on the server start.
Creating and loading an already connected drive is the most technically challenging tasks in any connector. You will need to provide an implementation of
based on ready abstract class . This already offers helpers for low-level work with JCR in ECMS Documents. It also implements all its operations on the drive as asynchronous commands—you will only extend and fill them with the logic. There is also an abstract implementation of , created as a facade to file operations on the cloud side. This abstract drive also manages user credentials renewal (if the cloud service requires this). In general, it is a main class in a connector’s internals.As a developer of a connector, you will need to implement commands for connection and synchronization, file operations in the cloud, drive state (if required), and storage and loading of user credentials. A stub code of this implementation, with recommendations in Javadocs, can be found in the template project, in JCRLocalTemplateDrive class. Also feel free to look at the code of other existing connectors (e.g. Google Drive or Box): they are good examples of how to solve provider-specific needs.
User Interface
To become available to users, the Cloud Drive requires user interface (UI) integration with ECMS Documents. This integration lies in ecms artifact and adds required menu and file actions to the existing UI. All parts of the UI can be split on UI extensions (menu, dialogs, views) added via server configuration and client styles with Javascript modules (to be discussed in Part 3). Both UI extensions and client support are optional for a connector. A minimal connector can work with what Cloud Drive already has: an item for a new cloud provider in Connect Cloud Documents dialog, menu items on drive and files, and automatic synchronization. All files will have default eXo icons and if they are general file types it’s a good idea to use the Platform defaults. But in some cases, when your cloud service has file types that are proprietary or not registered in eXo, it will be useful to provide dedicated icons for them. Cloud Drive allows this in a most simple way: just drop the files in the expected place in a WAR file (as conventions prescribe).
The connector may also propose a dedicated menu action to connect drives to its users. Default dialog with all available providers may not be efficient for enterprise use cases, and the Platform administrator may prefer to give some users a way to connect only drives of a particular type. It is simple to implement such a menu, as the core add-on already has a base class to extend with minimal coding. Below is a sample “Connect My Cloud” action; the only thing you need to provide is the provider ID of your connector:
@ComponentConfig(events = { @EventConfig(listeners = ConnectMycloudActionComponent.ConnectMycloudActionListener.class) }) public class ConnectMycloudActionComponent extends BaseConnectActionComponent { /** * My Cloud id from configuration - mycloud. * */ protected static final String PROVIDER_ID = "mycloud"; public static class ConnectMycloudActionListener extends UIActionBarActionListener<ConnectMycloudActionComponent> { public void processEvent(Event<ConnectMycloudActionComponent> event) throws Exception { } } /** * {@inheritDoc} */ @Override protected String getProviderId() { return PROVIDER_ID; // return actual provider ID } }
Web services
Another, also optional, part of the Java API is its web services for custom operations (which will also be discussed as part of Javascript API in Part 3). The eXo solution offers a simple and efficient way to create RESTful services using its embedded JAX-RS engine (eXo WS). A service is a component in an eXo container; it can refer Cloud Drive components, as with many other eXo features, via dependency injection. Below is a simplified version of a REST-service for a sample that returns comments for a file:
package org.exoplatform.clouddrive.mycloud.rest; .... /** * Sample RESTful service to provide some specific features of "mycloud" connector. */ @Path("/clouddrive/drive/mycloud") @Produces(MediaType.APPLICATION_JSON) public class SampleService implements ResourceContainer { protected static final Log LOG = ExoLogger.getLogger(SampleService.class); protected final CloudDriveFeatures features; protected final CloudDriveService cloudDrives; protected final RepositoryService jcrService; protected final SessionProviderService sessionProviders; /** * Component constructor. * * @param cloudDrives * @param features * @param jcrService * @param sessionProviders */ public SampleService(CloudDriveService cloudDrives, CloudDriveFeatures features, RepositoryService jcrService, SessionProviderService sessionProviders) { this.cloudDrives = cloudDrives; this.features = features; this.jcrService = jcrService; this.sessionProviders = sessionProviders; } /** * Return comments of a file from cloud side. * * @param uriInfo * @param workspace * @param path * @param providerId * @return */ @GET @Path("/comments/") @RolesAllowed("users") public Response getFileComments(@Context UriInfo uriInfo, @QueryParam("workspace") String workspace, @QueryParam("path") String path) { if (workspace != null) { if (path != null) { // TODO Get a cloud file and return collection of comments on the file. try { CloudDrive local = cloudDrives.findDrive(workspace, path); if (local != null) { List<Object> comments = new ArrayList<Object>(); try { CloudFile file = local.getFile(path); // TODO fill collection of comments on the file. comments.add(new Object()); } catch (NotCloudFileException e) { // we assume: not yet uploaded file has no remote comments } return Response.ok().entity(comments).build(); } return Response.status(Status.NO_CONTENT).build(); } catch (LoginException e) { LOG.warn("Error login to read drive file comments " + workspace + ":" + path + ": " + e.getMessage()); return Response.status(Status.UNAUTHORIZED).entity("Authentication error.").build(); } catch (CloudDriveException e) { LOG.warn("Error reading file comments " + workspace + ":" + path, e); return Response.status(Status.BAD_REQUEST).entity("Error reading file. " + e.getMessage()).build(); } catch (RepositoryException e) { LOG.error("Error reading file comments " + workspace + ":" + path, e); return Response.status(Status.INTERNAL_SERVER_ERROR) .entity("Error reading file comments: storage error.") .build(); } catch (Throwable e) { LOG.error("Error reading file comments " + workspace + ":" + path, e); return Response.status(Status.INTERNAL_SERVER_ERROR) .entity("Error reading file comments: runtime error.") .build(); } } else { return Response.status(Status.BAD_REQUEST).entity("Null path.").build(); } } else { return Response.status(Status.BAD_REQUEST).entity("Null workspace.").build(); } } }
The custom web services component of eXo was designed to make creation of services for specific features possible. This way you can implement custom logic on the server side and invoke it from a client.
That’s all for this post! In the next post, I will finish the Connector API introduction by covering its Javascript API, configuration, and packaging.
Join the eXo tribe by registering for the community and get updates, tutorials, support, and access to the Platform and add-on downloads!
Make the most out of eXo Platform 4
Register to the next webinar and get a complete overview of what you can do with eXo Platform 4. Reserve your seat now!