Content Hosting Handler

Content Hosting Handler

This Sub Project in resources is intended to provide the ability to mount other exterrnal resources within Content Hosting Service. These might include FileSystem, NFS,AFS, DSpace,Fedora, SRB resource as well as packaged resources such as ZIP, IMS-CP etc. It does not completely address resources that have players, IMS-CP, SCORM since it does not explicitly look at the helper mechanism.

Specification

Introduction

Content Hosting Service currently is capable of storing content collections (CC) and content resources (CR). It stores these items inside the Content Hosting Service (CHS) and exposes the API implementation of CC and CR objects via the CHS.

This document describes the extension of the current operation to allow CR Entities to act as proxies for virtual content resource and virtual content collection items stored outside the CHS implementation. This will allow the CHS to expose external digital repositories such as DSpace, Fedora, SRB, AFS, NFS and content systems such as iTunesU inside Sakai CHS as well as providing Content Handlers for resource as had been done in previous patches to the CHS.

The implementation of this extension is achieved by a small extension to the CHS API's that allow a Content Hosting Handler Bean (CHHB) to be associated with a CR that is stored in the CHS. Implementations of the CHHB API are injected into the Sakai Component Manager as either full components or as part of webapp wars. The CHHB implementations are looked up by the CR implementation as the CR is constructed.

Content Hosting Handler Bean

The CHHB implements an API, the main function of which is to deliver the virtual contents of the CR. When the getVirtualContentEntity()method is called a proxy implementation of either CC Entity or CR Entity is returned that represents the content in question.

Content Hosting Handler Bean Configuration

The CHHB is defined by a property (sakai:contenthandlerid) of the CR that defines the virtual CR or CC. This property if present contains the spring ID of the bean that handles the content. The CHS when it constructs the CR will lookup the bean in spring. The bean must implement the CHHB API.

Operation

When a CR is retrieved from storage it is passed via a ContentHostingResolver that determines if the CR in question requires translation into a Virtual Content Entity. If it does, the ContentHostingResolver performs that translation by creating a Virtual Content Entity. It is down to the implementation of the ContentHostingResolver how this is perform, but in the prototype implementation that we have, this is performed by routing requests to the appropriate ContentHostingHandler implementation.
The operation is performed in the base DbContentService where ContentEntities are retrieved from storage. This ensures that all name resolution and routing is performed at the Storage level.

Resolution of Sub Path

Depending on the implementation of the CHHB, the space within the Virtual CR may be addressable. If it is, then it is addressed by extending the URI of the CR. This means that on receipt of a URI, the CHS must attempt to locate the most appropriate CR that maps to the URI. It should do this by shorting the URI by removing path elements until a CR is found. This is performed within the ContentHostingResolver implementtion that has direct access to storage ensuring that the lookups can be optimised.
E.g.

/content/group/fef-fecd-23fe-cdcd/afolder/with/resource.zip/that/contains/some/files.txt  -- not found by CHS
/content/group/fef-fecd-23fe-cdcd/afolder/with/resource.zip/that/contains/some -- not found by CHS
/content/group/fef-fecd-23fe-cdcd/afolder/with/resource.zip/that/contains  -- not found by CHS
/content/group/fef-fecd-23fe-cdcd/afolder/with/resource.zip/that  -- not found by CHS
/content/group/fef-fecd-23fe-cdcd/afolder/with/resource.zip  -- found by CHS (contentResource1)
ContentEntity ce = contentResource1.getVirtualContentEntity();
ContentResource finalContent = ce.getPath("/that/contains/some/files.txt");

Issues

Revise: Where a resource is rendered as a collection, there needs to be some way of editing the resource. It is possible that revise function exposed in the Resources tool would use the Content Resource revise mechanism, whilst the Content Resource behaves like a Content Collection in the hierarchical view of the Resources tool. It is possible a Sakai 'helper' could be used to support the revise operation allowing the standard Resources Tool to hand off the Add new and Revise functionality to the registered helper.
View: Since the Virtual Content Entity may not have a resources view representation there need to be a mechanism to 'view' the resource. Again a registered sakai 'helper' could provide this functionality. The name of the helper being derived from a property of the resource or from a the CHHB itself.

Revise and view have not been specified at this stage as there is other work in progress in the OSP and Resources area on this work which we may be able to reuse in this context.

Embedded Content: Where an embedded Content Collection can contain resources that may be handled by a second CHHB there needs to be a mechanism for ensuring that the correct CHHB is used. For instance and IMS Content Package residing inside a DSpace collection, should behave in a similar way to an IMS-CP in the Sakai CHS storage. This could be achieved by setting the correct meta data in DSpace, but this will be problematic and require user intervention. It may be more appropriate to trigger some CHHB's on other properties of the CR such as mime type.

Implementation Examples

The following implementation examples show what could be achieved with the proposed extension to CHS. The intention if to implement the framework that allows these extensions but to leave it to the community to implement some of these extensions as the demand develops.

File system CHH


This CHH will allow the mounting of a normal file system visible from the Sakai cluster nodes inside CHS service. It differs slightly from the core CHS implementation in that the structure of the file system exactly maps to the view inside the CHS and the file system will be accessible via all the normal OS level tools. It presents issues over the storage of properties that do not normally map to standard file system properties. This CHH will make it easier to test the CHS implementation as it will not require configuration of other servers.

Configuration.

The configuration specifies a file system location on the local system, and a location where properties associated with the files might be stored. Folders map to CC Objects and Files map to CR Objects. There are 3 options for storing properties 1) stored in .chs/filename.xml files on the file system along side the files, 2) in a subfolder in the base stored in filenames derived from SHA1 hashes of the path 3) in the Sakai DB in a separate table.

Issues.

There will be issues over transactional control of the SHA1 properties files.
There will be transactional issues with the .chs/filename.xml files on the local disk.
Properties in the DB could easily become detached from the storage.

DSpace LNI CHH


The DSpace LNI CHH will use the DSpace LNI protocol to expose a DSpace collection inside Sakai. Initially this will be a read only exposure. Properties will be stored inside DSpace as part of DSpace's meta data storage. The view into the a Virtual Content Entity delivered from form the DSpace LNI CHH will mirror the structure within the selected DSpace collection.

Configuration

The configuration needs to contain the connection and AuthN details of the connection to DSpace. It may also need to contain mapping information between the properties held in DSpace and the CHS properties.

Issues

Since there is a wire protocol involved that may have significant latency this CHH will need to have significant caching to ensure acceptable response.

iTuneU CHH


The iTunesU CHH will allow an institution to mount an iTuneU space within DSpace so that media files and resources can be published from Sakai directly into iTunesU. The implementation will build on the DSpace LNI CHH replacing the LNI protocol layer with a a OSID-DR implementation that binds to the iTunesU location.

IMS CP CHH

The CHH implementations that have been proposed so far represent the presentation of DR like resource inside Sakai CHS. IMS CP CHH represents a rendered view into a resource stored within CHS. It will deliver CC and CR as before, but the CC and CR resources will produce a rendered view of the content when the Sakai Entity getHttpAccess() method is invoked.

Scorm CHH

The SCORM CHH will enable a SCORM server side component to manage a SCORM resource inside Sakai.

Zip CHH

This is similar to the IMS CP and SCORM implementation, except that instead of producing a rendered CR at the root of the Virtual Entity, it will deliver a CC.

Interfaces

ContentHostingHandler

org.sakaiproject.content.api.ContentHostingHandler
/**
 *
 */
package org.sakaiproject.content.api;

import java.io.InputStream;
import java.util.List;

import org.sakaiproject.exception.ServerOverloadException;

/**
 * @author ieb
 */
public interface ContentHostingHandler
{

        /**
         * Cancel an edit to a collection, if this needs to be done in the impl.
         * @param edit
         */
        void cancel(ContentCollectionEdit edit);

        /**
         * cancel an edit to a resource ( if this needs to be done )
         * @param edit
         */
        void cancel(ContentResourceEdit edit);

        /**
         * commit a collection
         * @param edit
         */
        void commit(ContentCollectionEdit edit);

        /**
         * commit a resource
         * @param edit
         */
        void commit(ContentResourceEdit edit);

        /**
         * commit a deleted resource
         * @param edit
         * @param uuid
         */
        void commitDeleted(ContentResourceEdit edit, String uuid);

        /**
         * get a list of collections contained within the supplied collection
         * @param collection
         * @return
         */
        List getCollections(ContentCollection collection);

        /**
         * get a ContentCollectionEdit for the ID, creating it if necessary, this should not perisist
         * untill commit is invoked
         * @param id
         * @return
         */
        ContentCollectionEdit getContentCollectionEdit(String id);

        /**
         * get a content resource edit for the supplied ID, creating it if necesary. This sould not persist
         * intill commit is invoked.
         * @param id
         * @return
         */
        ContentResourceEdit getContentResourceEdit(String id);

        /**
         * get a list of string ids of all resources below this point
         * @param ce
         * @return
         */
        List getFlatResources(ContentEntity ce);

        /**
         * get the resource body
         * @param resource
         * @return
         * @throws ServerOverloadException
         */
        byte[] getResourceBody(ContentResource resource)
                        throws ServerOverloadException;

        /**
         * get a list of resource ids as strings within the collection,
         * @param collection
         * @return
         */
        List getResources(ContentCollection collection);

        /**
         * convert the passed in ContentEntity into a virtual Content Entity. The
         * implimentation should check that the passed in entity is managed by this content handler
         * before performing the translation. Additionally it must register the content handler
         * with the newly proxied ContentEntity so that subsiquent invocations are routed back
         * to the corretct ContentHostingHandler implimentation
         *
         * @param edit
         * @return
         */
        ContentEntity getVirtualContentEntiy(ContentEntity edit);
    
        /**
         * perform a wastebasket operation on the names id, if the implementation supports the operation
         * otherwise its safe to ignore.
         * @param id
         * @param uuid
         * @param userId
         * @return
         */
        ContentResourceEdit putDeleteResource(String id, String uuid, String userId);


        /**
         * remove the supplied collection
         * @param edit
         */
        void removeCollection(ContentCollectionEdit edit);

        /**
         * remove the resource
         * @param edit
         */
        void removeResource(ContentResourceEdit edit);

        /**
         * stream the body of the resource
         * @param resource
         * @return
         * @throws ServerOverloadException
         */
        InputStream streamResourceBody(ContentResource resource)
                        throws ServerOverloadException;



}


ContentHostingHandlerResolver

org.sakaiproject.content.api.ContentHostingHandlerResolver
/**
 *
 */
package org.sakaiproject.content.impl;

import java.io.InputStream;
import java.util.List;

import org.sakaiproject.content.api.ContentCollection;
import org.sakaiproject.content.api.ContentCollectionEdit;
import org.sakaiproject.content.api.ContentResource;
import org.sakaiproject.content.api.ContentResourceEdit;
import org.sakaiproject.content.impl.BaseContentService.Storage;
import org.sakaiproject.exception.ServerOverloadException;

/**
 * <p>
 * The ContentHostingHandlerResolver performs operations at the ContentHostingSerivice
 * storage area to resolve operations int he sotrage area to the correct location. This can
 * either be the default storage implementation or the ContentHostingHandler associated with
 * nodes in the path to the ContentEntiy in question.
 * </p><p>
 * Implementors should be aware that their may be heavy access to this component on a per
 * request basis, so they might want to consider a chacing mechnims if the resolution of
 * ContentEntitties is expensive.
 * </p>
 * @author ieb
 */
public interface ContentHostingHandlerResolver
{

        /**
         * resolves the cancelCollection opertion in storage
         * @param storage
         * @param edit
         */
        void cancelCollection(Storage storage, ContentCollectionEdit edit);

        /**
         * resolves the cancelResource operation in storage
         * @param storage
         * @param edit
         */
         void cancelResource(Storage storage, ContentResourceEdit edit);

        /**
         * resolves the checkCollection operation in storage
         * @param storage
         * @param id
         * @return
         */
         boolean checkCollection(Storage storage, String id);

        /**
         *
         * @param storage
         * @param id
         * @return
         */
        boolean checkResource(Storage storage, String id);

        /**
         *
         * @param storage
         * @param edit
         */
        void commitCollection(Storage storage, ContentCollectionEdit edit);

        /**
         *
         * @param storage
         * @param edit
         * @param uuid
         */
        void commitDeleteResource(Storage storage, ContentResourceEdit edit,
                        String uuid);

        /**
         *
         * @param storage
         * @param edit
         * @throws ServerOverloadException
         */
        void commitResource(Storage storage, ContentResourceEdit edit)
                        throws ServerOverloadException;

        /**
         *
         * @param storage
         * @param id
         * @return
         */
        ContentCollectionEdit editCollection(Storage storage, String id);

        /**
         *
         * @param storage
         * @param id
         * @return
         */
        ContentResourceEdit editResource(Storage storage, String id);


        /**
         *
         * @param storage
         * @param id
         * @return
         */
        ContentCollection getCollection(Storage storage, String id);

        /**
         *
         * @param storage
         * @param collection
         * @return
         */
        List getCollections(Storage storage, ContentCollection collection);

        /**
         *
         * @param storage
         * @param collectionId
         * @return
         */
        List getFlatResources(Storage storage, String collectionId);

        /**
         *
         * @param storage
         * @param id
         * @return
         */
        ContentResource getResource(Storage storage, String id);

        /**
         *
         * @param storage
         * @param resource
         * @return
         * @throws ServerOverloadException
         */
        byte[] getResourceBody(Storage storage, ContentResource resource) throws ServerOverloadException;

        /**
         *
         * @param storage
         * @param collection
         * @return
         */
        List getResources(Storage storage, ContentCollection collection);

        /**
         *
         * @param storage
         * @param id
         * @return
         */
        ContentCollectionEdit putCollection(Storage storage, String id);

        /**
         *
         * @param storage
         * @param uuid
         * @param userId
         * @param object
         * @return
         */
        ContentResourceEdit putDeleteResource(Storage storage, String id,  String uuid,
                        String userId);

        /**
         *
         * @param storage
         * @param id
         * @return
         */
        ContentResourceEdit putResource(Storage storage, String id);

        /**
         *
         * @param storage
         * @param edit
         */
        void removeCollection(Storage storage, ContentCollectionEdit edit);

        /**
         *
         * @param storage
         * @param edit
         */
        void removeResource(Storage storage, ContentResourceEdit edit);

        /**
         *
         * @param storage
         * @param resource
         * @return
         * @throws ServerOverloadException
         */
        InputStream streamResourceBody(Storage storage, ContentResource resource) throws ServerOverloadException;

        // Object delResourceBodyFilesystem(Storage storage, ContentResourceEdit
        // resource);

        // void delResourceBodyDb(Storage storage, ContentResourceEdit resource);

}