/*
* ConcourseConnect
* Copyright 2009 Concursive Corporation
* http://www.concursive.com
*
* This file is part of ConcourseConnect, an open source social business
* software and community platform.
*
* Concursive ConcourseConnect is free software: you can redistribute it and/or
* modify it under the terms of the GNU Affero General Public License as published
* by the Free Software Foundation, version 3 of the License.
*
* Under the terms of the GNU Affero General Public License you must release the
* complete source code for any application that uses any part of ConcourseConnect
* (system header files and libraries used by the operating system are excluded).
* These terms must be included in any work that has ConcourseConnect components.
* If you are developing and distributing open source applications under the
* GNU Affero General Public License, then you are free to use ConcourseConnect
* under the GNU Affero General Public License.
*
* If you are deploying a web site in which users interact with any portion of
* ConcourseConnect over a network, the complete source code changes must be made
* available. For example, include a link to the source archive directly from
* your web site.
*
* For OEMs, ISVs, SIs and VARs who distribute ConcourseConnect with their
* products, and do not license and distribute their source code under the GNU
* Affero General Public License, Concursive provides a flexible commercial
* license.
*
* To anyone in doubt, we recommend the commercial license. Our commercial license
* is competitively priced and will eliminate any confusion about how
* ConcourseConnect can be used and distributed.
*
* ConcourseConnect is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
* FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more
* details.
*
* You should have received a copy of the GNU Affero General Public License
* along with ConcourseConnect. If not, see <http://www.gnu.org/licenses/>.
*
* Attribution Notice: ConcourseConnect is an Original Work of software created
* by Concursive Corporation
*/
package com.concursive.connect.web.modules.setup.utils;
import bsh.Interpreter;
import com.concursive.commons.api.APIConnection;
import com.concursive.commons.api.APIRestore;
import com.concursive.commons.codec.PasswordHash;
import com.concursive.commons.db.ConnectionElement;
import com.concursive.commons.db.ConnectionPool;
import com.concursive.commons.db.DatabaseUtils;
import com.concursive.commons.text.StringUtils;
import com.concursive.commons.xml.XMLUtils;
import com.concursive.connect.Constants;
import com.concursive.connect.cache.CacheContext;
import com.concursive.connect.cache.Caches;
import com.concursive.connect.cache.utils.CacheUtils;
import com.concursive.connect.config.ApplicationPrefs;
import com.concursive.connect.web.modules.api.beans.PacketContext;
import com.concursive.connect.web.modules.api.dao.SyncTableList;
import com.concursive.connect.web.modules.api.utils.TransactionUtils;
import com.concursive.connect.web.modules.login.dao.User;
import com.concursive.connect.web.modules.login.dao.UserList;
import com.concursive.connect.web.modules.members.dao.TeamMember;
import com.concursive.connect.web.modules.profile.dao.*;
import com.concursive.connect.web.modules.upgrade.utils.UpgradeUtils;
import com.concursive.connect.web.utils.LookupList;
import net.sf.ehcache.CacheManager;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import javax.servlet.ServletContext;
import java.io.*;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.Statement;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.LinkedHashMap;
/**
* Utilities for setting up and validating a ConcourseConnect installation
*
* @author matt rajkowski
* @version $Id$
* @created Nov 29, 2005
*/
public class SetupUtils {
private static Log LOG = LogFactory.getLog(SetupUtils.class);
public final static String fs = System.getProperty("file.separator");
/**
* This method is used by the ant task to install any default data
*
* @param args database connection settings: driver, url, user, password
*/
public static void main(String[] args) {
// Connection values
String driver = args[0];
String url = args[1];
String user = args[2];
String password = args[3];
String fileLibraryPath = args[4];
String title = args[5];
String description = args[6];
String keywords = args[7];
String purpose = args[8];
String userfirst = args[9];
String userlast = args[10];
String useremail = args[11];
String userpass = args[12];
// Insert default data
ConnectionPool cp = new ConnectionPool();
Connection db = null;
try {
ConnectionElement ce = new ConnectionElement(url, user, password);
ce.setDriver(driver);
db = cp.getConnection(ce, true);
// Instantiate the caches
CacheManager.create();
CacheContext cacheContext = new CacheContext();
cacheContext.setUpgradeConnection(db);
Caches.addCaches(cacheContext);
// Load the object map and services
int SYSTEM_ID = 1;
SyncTableList syncTableList = new SyncTableList();
syncTableList.setSystemId(SYSTEM_ID);
// Load the core object map
syncTableList.loadObjectMap(SyncTableList.class.getResourceAsStream("/object_map.xml"));
// Load plug-in mappings in the services path
File[] serviceFiles = new File("src/main/webapp/WEB-INF/services").listFiles();
if (serviceFiles != null && serviceFiles.length > 0) {
for (File thisFile : serviceFiles) {
if (thisFile.getAbsolutePath().endsWith(".xml")) {
try {
LOG.info("Adding services from... " + thisFile.getAbsolutePath());
syncTableList.loadObjectMap(new FileInputStream(thisFile));
} catch (Exception e) {
LOG.error("getObjectMap exception", e);
}
}
}
}
// The default category information
insertDefaultCategories(db, syncTableList, fileLibraryPath);
// Insert admin user
if (useremail != null && userpass != null) {
User adminUser = new User();
adminUser.setFirstName(userfirst);
adminUser.setLastName(userlast);
adminUser.setUsername(useremail);
adminUser.setEmail(useremail);
adminUser.setPassword1(userpass);
adminUser.setPassword2(userpass);
insertDefaultAdmin(db, adminUser, null, null);
}
// The default system profile
Project project = new Project();
project.setTitle(title);
project.setShortDescription(description);
project.setKeywords(keywords);
insertDefaultSiteConfig(db, fileLibraryPath, project, purpose, new ApplicationPrefs());
// The default profile content
insertDefaultContent(db, syncTableList, fileLibraryPath, project);
} catch (Exception e) {
e.printStackTrace(System.out);
LOG.error(e);
System.exit(2);
} finally {
LOG.info("Cleaning up...");
CacheManager.getInstance().shutdown();
cp.free(db);
}
System.exit(0);
}
/**
* Determines if the application is fully configured
*
* @param prefs
* @return
*/
public static boolean isConfigured(ApplicationPrefs prefs) {
return prefs.isConfigured();
}
/**
* Determines if the services component of installation is fully configured
*
* @param prefs
* @return
*/
public static boolean isServicesConfigured(ApplicationPrefs prefs) {
return prefs.has(ApplicationPrefs.CONCURSIVE_SERVICES_ID);
}
/**
* Determines if the database schema has been created
*
* @param db
* @return
*/
public static boolean isDatabaseInstalled(Connection db) {
int count = -1;
try {
Statement st = db.createStatement();
ResultSet rs = st.executeQuery(
"SELECT count(*) AS recordcount " +
"FROM database_version ");
rs.next();
count = rs.getInt("recordcount");
rs.close();
st.close();
} catch (Exception e) {
}
return count > 0;
}
/**
* Determines if there is an administrative user configured in the database
*
* @param db
* @return
*/
public static boolean isAdminInstalled(Connection db) {
int count = -1;
try {
PreparedStatement pst = db.prepareStatement(
"SELECT count(*) AS recordcount " +
"FROM users " +
"WHERE access_admin = ? ");
pst.setBoolean(1, true);
ResultSet rs = pst.executeQuery();
rs.next();
count = rs.getInt("recordcount");
rs.close();
pst.close();
} catch (Exception e) {
}
return count > 0;
}
/**
* Determines if a default project has been installed
*
* @param db
* @return
*/
public static boolean isDefaultProjectInstalled(Connection db) {
int count = -1;
try {
PreparedStatement pst = db.prepareStatement(
"SELECT count(*) AS recordcount " +
"FROM projects " +
"WHERE system_default = ? ");
pst.setBoolean(1, true);
ResultSet rs = pst.executeQuery();
rs.next();
count = rs.getInt("recordcount");
rs.close();
pst.close();
} catch (Exception e) {
}
return count > 0;
}
/**
* Inserts the default database data
*
* @param db
* @param context
* @param setupPath
* @throws Exception
*/
public static void insertDefaultData(Connection db, ServletContext context, String setupPath) throws Exception {
// Prepare the BSH interpreter
Interpreter script = new Interpreter();
script.set("db", db);
// Default database inserts
InputStream source = context.getResourceAsStream(setupPath + "common" + fs + "install.bsh");
BufferedReader in = new BufferedReader(new InputStreamReader(source));
script.eval(in);
in.close();
// Module Defaults
String initPath = setupPath + "init" + fs;
DatabaseUtils.executeSQL(db, context.getResourceAsStream(initPath + "project.sql"), true);
DatabaseUtils.executeSQL(db, context.getResourceAsStream(initPath + "task.sql"), true);
DatabaseUtils.executeSQL(db, context.getResourceAsStream(initPath + "ticket.sql"), true);
// Bring the version database up-to-date with the upgrade utility
ArrayList<String> versionList = UpgradeUtils.retrieveDatabaseVersions(context.getResourceAsStream(setupPath + "database_versions.txt"));
for (String version : versionList) {
if (version.length() == 10) {
UpgradeUtils.addVersion(db, version);
}
}
}
public static void insertDefaultAdmin(Connection db, User thisUser, String ip, ApplicationPrefs prefs) throws Exception {
if (thisUser.getUsername() == null || thisUser.getPassword1() == null) {
throw new Exception("Missing required fields");
}
// Insert the user
thisUser.setPassword(PasswordHash.encrypt(thisUser.getPassword1()));
thisUser.setGroupId(1);
thisUser.setDepartmentId(1);
thisUser.setAccessAdmin(true);
thisUser.setAccessInvite(true);
thisUser.setAccessUserSettings(true);
thisUser.setAccessGuestProjects(true);
thisUser.setAccessAddProjects(true);
thisUser.setEnteredBy(0);
thisUser.setModifiedBy(0);
thisUser.setStartPage(1);
thisUser.setEnabled(true);
thisUser.setAccountSize("-1");
thisUser.insert(db, ip, prefs);
}
/**
* Inserts additional default data
*
* @param db
* @param syncTableList the mapping of objects and services
* @param fileLibraryPath
* @throws Exception
*/
public static void insertDefaultCategories(Connection db, SyncTableList syncTableList, String fileLibraryPath) throws Exception {
int SYSTEM_ID = 1;
// From the Service action method
PacketContext packetContext = new PacketContext();
packetContext.setObjectMap(syncTableList.getObjectMapping(SYSTEM_ID));
packetContext.setBaseFilePath(fileLibraryPath);
// Retrieve and stream the records to restore
LOG.debug("Loading the configuration xml...");
InputStream inputStream = SetupUtils.class.getResourceAsStream("/configuration_en_US.xml");
// Restore the state of the objects as-supplied, start by deleting the record and dependents
ArrayList<String> meta = new ArrayList<String>();
meta.add("mode=copy");
ArrayList exclude = new ArrayList();
HashMap replace = new HashMap();
replace.put("enteredBy", "1");
replace.put("modifiedBy", "1");
replace.put("thumbnailFilename", "null");
replace.put("logoId", "-1");
HashMap options = new HashMap();
options.put("exclude", exclude);
options.put("replace", replace);
// Use the APIConnection to do the shared work
LOG.debug("Parsing the records...");
APIConnection api = new APIConnection();
api.setAutoCommit(false);
api.setTransactionMeta(meta);
APIRestore.restore(api, inputStream, null, -1, options);
// Construct the xml
LOG.debug("Generating XML...");
XMLUtils xml = new XMLUtils(api.generateXMLPacket());
// Restore this item
TransactionUtils.processTransactions(db, xml, packetContext);
LOG.debug("Finished.");
}
public static void insertDefaultSiteConfig(Connection db, String fileLibraryPath, Project project, String purpose, ApplicationPrefs prefs) throws Exception {
LOG.debug("insertDefaultSiteConfig");
// Validate the pre-reqs
if (!StringUtils.hasText(project.getTitle()) ||
!StringUtils.hasText(project.getShortDescription()) ||
!StringUtils.hasText(project.getKeywords())) {
throw new Exception("Title, description, keywords are required");
}
// Load the categories
ProjectCategoryList categoryList = new ProjectCategoryList();
categoryList.setTopLevelOnly(true);
categoryList.buildList(db);
// Determine the default's category
int categoryId = categoryList.getIdFromValue("Groups");
// Determine the tabs
boolean isPrivate = false;
ProjectCategoryList validCategoryList = new ProjectCategoryList();
if ("social".equals(purpose)) {
validCategoryList.add(categoryList.getFromValue("Groups"));
validCategoryList.add(categoryList.getFromValue("People"));
validCategoryList.add(categoryList.getFromValue("Events"));
validCategoryList.add(categoryList.getFromValue("Ideas"));
validCategoryList.add(categoryList.getFromValue("Sponsors"));
} else if ("directory".equals(purpose)) {
validCategoryList.add(categoryList.getFromValue("Businesses"));
validCategoryList.add(categoryList.getFromValue("Organizations"));
validCategoryList.add(categoryList.getFromValue("Groups"));
validCategoryList.add(categoryList.getFromValue("People"));
validCategoryList.add(categoryList.getFromValue("Places"));
validCategoryList.add(categoryList.getFromValue("Events"));
validCategoryList.add(categoryList.getFromValue("Ideas"));
} else if ("community".equals(purpose)) {
validCategoryList.add(categoryList.getFromValue("Groups"));
validCategoryList.add(categoryList.getFromValue("People"));
validCategoryList.add(categoryList.getFromValue("Ideas"));
} else if ("intranet".equals(purpose)) {
isPrivate = true;
prefs.add(ApplicationPrefs.INFORMATION_IS_SENSITIVE, "true");
prefs.add(ApplicationPrefs.USERS_CAN_REGISTER, "false");
prefs.add(ApplicationPrefs.USERS_CAN_INVITE, "false");
prefs.add(ApplicationPrefs.USERS_CAN_START_PROJECTS, "true");
validCategoryList.add(categoryList.getFromValue("Groups"));
validCategoryList.add(categoryList.getFromValue("People"));
validCategoryList.add(categoryList.getFromValue("Events"));
validCategoryList.add(categoryList.getFromValue("Ideas"));
validCategoryList.add(categoryList.getFromValue("Projects"));
} else if ("projects".equals(purpose)) {
isPrivate = true;
prefs.add(ApplicationPrefs.INFORMATION_IS_SENSITIVE, "true");
prefs.add(ApplicationPrefs.USERS_CAN_REGISTER, "false");
prefs.add(ApplicationPrefs.USERS_CAN_INVITE, "false");
validCategoryList.add(categoryList.getFromValue("Groups"));
validCategoryList.add(categoryList.getFromValue("People"));
validCategoryList.add(categoryList.getFromValue("Projects"));
} else if ("web".equals(purpose)) {
validCategoryList.add(categoryList.getFromValue("Products"));
validCategoryList.add(categoryList.getFromValue("Services"));
validCategoryList.add(categoryList.getFromValue("Partners"));
validCategoryList.add(categoryList.getFromValue("Groups"));
validCategoryList.add(categoryList.getFromValue("People"));
validCategoryList.add(categoryList.getFromValue("Events"));
validCategoryList.add(categoryList.getFromValue("Ideas"));
validCategoryList.add(categoryList.getFromValue("Projects"));
}
// Update the related tabs, or leave all enabled
if (validCategoryList.size() > 0) {
for (ProjectCategory category : categoryList) {
category.setEnabled(validCategoryList.get(category) != null);
category.setSensitive(isPrivate);
category.update(db);
}
}
// Determine the profile visibility
project.setUpdateAllowGuests(true);
project.setAllowGuests(isPrivate ? false : true);
project.setUpdateAllowParticipants(true);
project.setAllowParticipants(true);
project.setUpdateMembershipRequired(true);
project.setMembershipRequired(false);
project.setApproved(true);
// Determine the tabs
project.setShowProfile(true);
project.getFeatures().setLabelProfile("Overview");
project.setShowNews(true);
project.getFeatures().setLabelNews("Blog");
project.setShowReviews(true);
project.setShowWiki(true);
project.setShowCalendar(true);
project.setShowDiscussion(true);
project.setShowDocuments(true);
project.setShowClassifieds(true);
project.setShowBadges(true);
project.setShowLists(true);
project.setShowIssues(true);
project.setShowTeam(true);
project.getFeatures().setLabelTickets("Issues");
project.getFeatures().setLabelTeam("Participants");
project.setShowMessages(true);
// Determine the record details
project.setUniqueId("main-profile");
project.setGroupId(1);
project.setCategoryId(categoryId);
project.setOwner(1);
project.setEnteredBy(1);
project.setModifiedBy(1);
project.insert(db);
project.updateFeatures(db);
project.setSystemDefault(true);
project.updateSystemDefault(db);
// Build a list of roles
LookupList roleList = CacheUtils.getLookupList("lookup_project_role");
// Build a list of the default permissions
PermissionList permissionList = new PermissionList();
permissionList.setProjectId(project.getId());
permissionList.buildList(db);
// Modify the permissions for the default profile
LinkedHashMap<String, Integer> permissionMap = new LinkedHashMap<String, Integer>();
permissionMap.put("project-tickets-view", TeamMember.MEMBER);
permissionMap.put("project-tickets-other", TeamMember.CHAMPION);
permissionMap.put("project-tickets-add", TeamMember.MEMBER);
permissionMap.put("project-tickets-edit", TeamMember.CHAMPION);
permissionMap.put("project-tickets-assign", TeamMember.CHAMPION);
permissionMap.put("project-tickets-close", TeamMember.CHAMPION);
permissionMap.put("project-tickets-delete", TeamMember.MANAGER);
for (String name : permissionMap.keySet()) {
Permission permission = permissionList.get(name);
permission.setUserLevel(roleList.getIdFromLevel(permissionMap.get(name)));
LOG.debug("Updating a permission: " + name);
permission.update(db);
}
// Get the admin role
int adminRowLevel = roleList.getIdFromValue("Manager");
// Add the admins as a member
UserList userList = new UserList();
userList.setAdmin(Constants.TRUE);
userList.buildList(db);
for (User thisUser : userList) {
TeamMember thisMember = new TeamMember();
thisMember.setProjectId(project.getId());
thisMember.setUserId(thisUser.getId());
thisMember.setUserLevel(adminRowLevel);
thisMember.setEnteredBy(1);
thisMember.setModifiedBy(1);
thisMember.insert(db);
}
}
public static void insertDefaultContent(Connection db, SyncTableList syncTableList, String fileLibraryPath, Project project) throws Exception {
LOG.debug("insertDefaultContent");
int SYSTEM_ID = 1;
// From the Service action method
PacketContext packetContext = new PacketContext();
packetContext.setObjectMap(syncTableList.getObjectMapping(SYSTEM_ID));
packetContext.setBaseFilePath(fileLibraryPath);
// Retrieve and stream the records to restore
LOG.debug("Loading the configuration xml...");
InputStream inputStream = SetupUtils.class.getResourceAsStream("/content_en_US.xml");
// Restore the state of the objects as-supplied, start by deleting the record and dependents
ArrayList<String> meta = new ArrayList<String>();
meta.add("mode=copy");
ArrayList exclude = new ArrayList();
HashMap<String, String> replace = new HashMap<String, String>();
replace.put("enteredBy", String.valueOf(project.getEnteredBy()));
replace.put("modifiedBy", String.valueOf(project.getEnteredBy()));
replace.put("projectId", String.valueOf(project.getId()));
replace.put("linkItemId", String.valueOf(project.getId()));
replace.put("thumbnailFilename", "null");
replace.put("logoId", "-1");
HashMap options = new HashMap();
options.put("exclude", exclude);
options.put("replace", replace);
// Use the APIConnection to do the shared work
LOG.debug("Parsing the records...");
APIConnection api = new APIConnection();
api.setAutoCommit(false);
api.setTransactionMeta(meta);
APIRestore.restore(api, inputStream, null, -1, options);
// Construct the xml
LOG.debug("Generating XML...");
XMLUtils xml = new XMLUtils(api.generateXMLPacket());
// Restore this item
TransactionUtils.processTransactions(db, xml, packetContext);
LOG.debug("Finished.");
}
}