Source: jive-sdk-service/lib/extension/extension.js

/*
 * Copyright 2013 Jive Software
 *
 *    Licensed under the Apache License, Version 2.0 (the "License");
 *    you may not use this file except in compliance with the License.
 *    You may obtain a copy of the License at
 *
 *       http://www.apache.org/licenses/LICENSE-2.0
 *
 *    Unless required by applicable law or agreed to in writing, software
 *    distributed under the License is distributed on an "AS IS" BASIS,
 *    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *    See the License for the specific language governing permissions and
 *    limitations under the License.
 */

/**
 * @module extensions
 */

////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

var fs = require('fs'),
    q  = require('q'),
    jive  = require('../../api'),
    _ = require("underscore");

////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// public

/**
 * @returns {Promise} Promise
 */
exports.find = function() {
    return jive.service.persistence().find( 'jiveExtension', { 'id' : 'jiveExtension' } ).then( function( records ) {
        if ( records && records.length > 0 ) {
            return records[0];
        }
        return null;
    });
};

/**
 * @param record
 * @returns {Promise} Promise
 */
exports.save = function(record) {
    return jive.service.persistence().save('jiveExtension', 'jiveExtension', record);
};

/**
 * @param rootDir
 * @param tilesDir
 * @param appsDir
 * @param cartridgesDir
 * @param storagesDir
 * @returns {Promise} Promise
 */
exports.prepare = function (rootDir, tilesDir, appsDir, cartridgesDir, storagesDir) {
    var extensionSrcDir = ( rootDir ? rootDir + '/' : '' ) + 'extension_src';
    var extensionZipDir = ( rootDir ? rootDir + '/' : '' ) + 'extension.zip';

    return jive.util.fsexists(extensionSrcDir).then(function( exists ) {
        if ( !exists ) jive.util.fsmkdir( extensionSrcDir)
    }).then( function() {
        return getPersistedExtensionInfo(jive.service.options['extensionInfo'] || {});
    }).then( function(extensionInfo) {
        return jive.util.recursiveCopy( __dirname + "/template", extensionSrcDir)
            .then( function() {
                return getTileDefinitions();
            }).then( function(definitions) {
                return setupExtensionDefinitionJson(
                    tilesDir,
                    appsDir,
                    cartridgesDir,
                    storagesDir,
                    extensionSrcDir,
                    extensionInfo,
                    definitions
                ).then( function(definitionsJson) {
                    // persist the extension metadata
                    var meta = fillExtensionMetadata(extensionInfo, definitionsJson);
                        var stringifiedMeta = JSON.stringify(meta, null, 4);
                        jive.logger.debug("Extension meta: \n" + stringifiedMeta);
                        return jive.util.fswrite( stringifiedMeta, extensionSrcDir  + '/meta.json' );
                });
            });
    }).then( function() {
        // zip it all
        return jive.util.zipFolder( extensionSrcDir, extensionZipDir );
    }).catch( function(e) {
        var error = new Error(e);
        var stack = error.stack;
        jive.logger.error( stack );
        throw error;
    });
};

////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// private

function isValid(file ) {
    return !(file.indexOf('.') == 0)
}

function limit(str, chars) {
    return str.substring(0, str.length > chars ? chars : str.length );
}

function fillExtensionMetadata(extensionInfo, definitions) {

    var description = extensionInfo['description'];
    var name = extensionInfo['name'];
    var type = 'client-app'; // by default
    var id = extensionInfo['uuid'];

    var hasCartridges = definitions['jabCartridges'] && definitions['jabCartridges'].length > 0;
    var hasOsapps = definitions['osapps'] && definitions['osapps'].length > 0;
    var hasTiles = definitions['tiles'] && definitions['tiles'].length > 0;
    var hasTemplates = definitions['templates'] && definitions['templates'].length > 0;

    if ( hasCartridges && (hasOsapps || hasTiles || hasTemplates ) ) {
        throw Error("Extension cannot contain Jive Anywhere cartridges and other types.");
    }

    if ( hasCartridges ) {
        type = 'jab-cartridges-app';
    }

    if (!name) {
        // synthesize a reasonable name
        name = "Add-on containing ";
        var c = [];
        if ( hasCartridges ) {
            c.push('cartridge(s)');
        }
        if ( hasOsapps ) {
            c.push('apps(s)');
        }
        if ( hasTiles ) {
            c.push('tiles(s)');
        }
        name += c.join(', ').trim();
    }

    if (!description) {
        description = '';
        // synthesize a reasonable description
        if ( hasCartridges ) {
            var c = [];
            definitions['jabCartridges'].forEach( function(cartridge) {
                c.push(cartridge['name']);
            });
            description += 'Cartridges: [';
            description += c.join(', ').trim();
            description += '] ';
        }

        if ( hasTiles ) {
            var c = [];
            definitions['tiles'].forEach( function(tile) {
                c.push(tile['name']);
            });
            description += 'Tiles: [';
            description += c.join(', ').trim();
            description += '] ';
        }

        if ( hasOsapps ) {
            var c = [];
            definitions['osapps'].forEach( function(osapp) {
                c.push(osapp['name']);
            });
            description += 'Apps: [';
            description += c.join(', ').trim();
            description += '] ';
        }

        description = description.trim();
        description = limit(description, 255);
    }

    return _.defaults({
        "package_version": extensionInfo['packageVersion'] || '1.0',
        "id": id,
        "type": type,
        "name": name,
        "description": description,
        "minimum_version": extensionInfo['minJiveVersion'] || '0000',
        "icon_16": "extension-16.png",
        "icon_48": "extension-48.png",
        "icon_128": "extension-128.png",
        "status": "available",
        "released_on": "2013-03-08T19:11:11.234Z", // xxx todo
        "register_url": jive.service.serviceURL() + "/jive/oauth/register",
        "service_url": jive.service.serviceURL(),
        "redirect_url": extensionInfo['redirectURL'] || jive.service.serviceURL()
    }, jive.service.options['extensionInfo']);
}

function getTileDefinitions() {
    var finalizeRequest = function (allDefinitions) {
        var toReturn = [];
        allDefinitions.forEach(function (batch) {
            toReturn = toReturn.concat(batch);
        });
        return q.resolve(jive.service.getExpandedTileDefinitions(toReturn));
    };
    return q.all([ jive.tiles.definitions.findAll(), jive.extstreams.definitions.findAll() ]).then(finalizeRequest);
}

function setupExtensionDefinitionJson(tilesDir, appsDir, cartridgesDir, storagesDir, extensionSrcDir, extensionInfo, definitions) {
    return getTemplates(tilesDir).then(function (templates) {
        return getApps(appsDir, extensionInfo).then(function (apps) {
            return getStorages(storagesDir, extensionInfo).then(function (storages) {
                return getCartridges(cartridgesDir, extensionSrcDir).then(function (cartridges) {

                    jive.logger.debug("apps:\n" + JSON.stringify(apps, null, 4));
                    jive.logger.debug("cartridges:\n" + JSON.stringify(cartridges, null, 4));

                    var definitionsJson = {
                        'integrationUser': {
                            'systemAdmin': extensionInfo['jiveServiceSignature'] ? true : false,
                            'jiveServiceSignature': extensionInfo['jiveServiceSignature']
                        },
                        'tiles': (definitions && definitions.length > 0) ? definitions : undefined,
                        'templates': templates,
                        'osapps': apps,
                        'storageDefinitions': storages,
                        'jabCartridges': cartridges
                    };

                    var definitionJsonPath = extensionSrcDir + '/definition.json';
                    var stringifiedDefinitionJson = JSON.stringify(definitionsJson, null, 4);
                    return jive.util.fswrite(stringifiedDefinitionJson, definitionJsonPath).then( function() {
                        return definitionsJson;
                    })
                });
            });
        });

    });
}

function getStorages(storagesDir, extensionInfo) {
    var storages = [];
    return jive.util.fsexists( storagesDir).then( function(exists) {
        if ( exists ) {
            return q.nfcall(fs.readdir, storagesDir).then(function(dirContents){
                var proms = [];
                dirContents.forEach(function(item) {
                    if ( isValid(item) ) {
                        var definitionDir = storagesDir + '/' + item + '/definition.json';
                        proms.push( jive.util.fsreadJson(definitionDir).then(function(storage){
                            if ( !storage['name'] ) {
                                // generate a storage name if one is not provided
                                storage['name'] = jive.util.guid(extensionInfo['uuid']);
                            }

                            return storage;
                        }) );
                    }
                });
                return q.all(proms);
            });
        } else {
            return storages;
        }
    });
}

function getApps(appsRootDir, extensionInfo) {
    var apps = [];
    return jive.util.fsexists( appsRootDir).then( function(exists) {
        if ( exists ) {
            return q.nfcall(fs.readdir, appsRootDir).then(function(dirContents){
                var proms = [];
                dirContents.forEach(function(item) {
                    if ( isValid(item) ) {
                        var definitionDir = appsRootDir + '/' + item + '/definition.json';
                        proms.push( jive.util.fsreadJson(definitionDir).then(function(app) {
                            if ( !app['name'] ) {
                                app['name'] = item;
                            }

                            if ( !app['id'] ) {
                                app['id'] = jive.util.guid(app['name'] + extensionInfo['uuid']);
                            }

                            if ( !app['appPath'] ) {
                                // generate an app path if one is not provided
                                var temp = app['id'];
                                temp = temp.replace(/[^a-zA-Z 0-9]+/g,'');
                                app['appPath'] = temp;
                            }

                            app['url'] = jive.service.serviceURL() + '/osapp/' + item + '/app.xml';
                            return app;
                        }) );
                    }
                });
                return q.all(proms);
            });
        } else {
            return apps;
        }
    });
}


function getCartridges(cartridgesRootDir, extensionSrcDir) {
    var cartridges = [];
    return jive.util.fsexists( cartridgesRootDir).then( function(exists) {
        if ( exists ) {
            return q.nfcall(fs.readdir, cartridgesRootDir).then(function(dirContents){
                var proms = [];
                dirContents.forEach(function(item) {
                    if ( isValid(item) ) {
                        var definitionDir = cartridgesRootDir + '/' + item + '/definition.json';
                        var contentDir = cartridgesRootDir + '/' + item + '/content';
                        proms.push( jive.util.fsreadJson(definitionDir).then(function(cartridge) {
                            if ( !cartridge['name'] ) {
                                // generate an cartidge name if one is not provided
                                cartridge['name'] = jive.util.guid(item);
                            }

                            var zipFileName = cartridge['zipFileName'];
                            if ( !zipFileName ) {
                                zipFileName = cartridge['name'];
                                zipFileName = zipFileName.replace(/[^\S]+/, '');
                                zipFileName = zipFileName.replace(/[^a-zA-Z0-9]/, '-');
                                zipFileName += '.zip';
                            }

                            var zipFile = extensionSrcDir + '/data/' + zipFileName;
                            return jive.util.zipFolder( contentDir, zipFile, true).then( function() {
                                return q.resolve(cartridge);
                            })
                        }) );
                    }
                });
                return q.all(proms);
            });
        } else {
            return cartridges;
        }
    });
}

function getTemplates(tilesDir) {
    var templatesPath = tilesDir + '/templates.json';

    var toTemplateArray = function(allDefinitions, templates) {
        if ( !allDefinitions || allDefinitions.length < 1) {
            return null;
        }

        if ( !templates ) {
            return null;
        }

        var templateArray = [];
        for (var key in templates) {
            if (templates.hasOwnProperty(key)) {
                var template = templates[key];
                var templateTiles = template['tiles'];
                var filteredTiles = [];
                if ( templateTiles ) {
                    templateTiles.forEach( function(tileName) {
                        if ( allDefinitions.indexOf(tileName) > -1 ) {
                            filteredTiles.push(tileName);
                        }
                    });
                }
                template['tiles'] = filteredTiles;
                templateArray.push(template);
            }
        }
        return templateArray;
    };

    return getAllDefinitions().then( function(allDefinitions) {
        return jive.util.fsexists(tilesDir).then(function(exists) {
            if ( !exists ) {
                return q.resolve([]);
            } else {
                return jive.service.extensions().find().then(function (extension) {
                    return jive.util.fsexists(templatesPath.trim()).then(function (exists) {
                        if (!exists) {
                            // create it
                            return buildDefaultTemplate(allDefinitions, extension).then( function(defaultTemplate) {
                                var templates = {};
                                templates['default'] = defaultTemplate;
                                return jive.util.fswrite(JSON.stringify(templates, null, 4), tilesDir + '/templates.json').then( function () {
                                    return toTemplateArray(allDefinitions, templates);
                                })
                            });
                        } else {
                            // make sure there is a default template
                            return jive.util.fsread(templatesPath).then( function(data) {
                                var templates = JSON.parse( new Buffer(data).toString() );
                                jive.logger.debug(templates);
                                if ( !templates['default'] ) {
                                    return buildDefaultTemplate(allDefinitions, extension).then( function(defaultTemplate) {
                                        templates['default'] = defaultTemplate;
                                        return jive.util.fswrite(JSON.stringify(templates, null, 4), tilesDir + '/templates.json').then( function() {
                                            return toTemplateArray(allDefinitions, templates);
                                        })
                                    });
                                } else {
                                    return toTemplateArray(allDefinitions, templates);
                                }
                            });
                        }
                    });
                });
            }
        })
    });
}

function getAllDefinitions() {
    return q.all([ jive.tiles.definitions.findAll(), jive.extstreams.definitions.findAll() ]).then(
        function (allDefinitions) {
            var tiles = [];
            allDefinitions.forEach(function (batch) {
                batch.forEach(function (b) {
                    tiles.push(b['name']);
                });
            });

        return tiles;
    });
}

function buildDefaultTemplate(allDefinitions, extension) {
    var extensionName = extension['name'];
    var extensionDescription = extension['description'];

    if ( !extensionName ) {
        if ( allDefinitions ) {
            var c = [];
            extensionName = '';
            extensionName += 'Contains: [';
            extensionName += allDefinitions.join(', ').trim();
            extensionName += '] ';

            extensionName = limit(extensionName, 127);
        } else {
            extensionName = extension['uuid'];
        }
    }

    if ( !extensionDescription ) {
        extensionDescription = 'Extension UUID ' + extension['uuid'] + ' ' + extensionName;
    }

    var defaultTemplate = {
        "name": "defaultTemplate",
        "displayName": extensionName,
        "description": extensionDescription,
        "tiles": allDefinitions
    };

    return q.resolve( defaultTemplate );
}

function getPersistedExtensionInfo(config) {
    return exports.find().then( function( record ) {
        if ( record ) {
            for (var key in config) {
                if (config.hasOwnProperty(key)) {
                    // update
                    if ( !record[key] ) {
                        record[key] = config[key];
                        if ( key == 'uuid') {
                            record[key] = jive.util.guid();
                        }
                    } else if ( config[key] && config[key] !== record[key] ) {
                        record[key] = config[key];
                    }
                }
            }

            return exports.save(record);
        } else {
            // create it
            record = {
                'id' : 'jiveExtension',
                'uuid' : jive.util.guid()
            };

            for (var key in config) {
                if (config.hasOwnProperty(key)) {
                    // update
                    if ( !record[key] ) {
                        record[key] = config[key];
                        if ( key == 'uuid') {
                            record[key] = jive.util.guid();
                        }
                    } else if ( config[key] && config[key] !== record[key] ) {
                        record[key] = config[key];
                    }
                }
            }

            return exports.save(record);
        }
    });
}