/**********************************************************************************
* $URL: $
* $Id: $
***********************************************************************************
*
* Author: Charles Hedrick, hedrick@rutgers.edu
*
* Copyright (c) 2010 Rutgers, the State University of New Jersey
*
* Licensed under the Educational Community 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.opensource.org/licenses/ECL-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.
*
**********************************************************************************/
package org.sakaiproject.lessonbuildertool.service;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.SortedSet;
import java.util.TreeSet;
import java.util.Comparator;
import java.util.Date;
import javax.servlet.ServletRequest;
import javax.servlet.ServletContext;
import org.sakaiproject.lessonbuildertool.tool.beans.SimplePageBean;
import org.sakaiproject.lessonbuildertool.tool.beans.SimplePageBean.UrlItem;
import org.sakaiproject.lessonbuildertool.SimplePageItem;
import org.sakaiproject.site.api.Site;
import org.sakaiproject.site.api.ToolConfiguration;
import org.sakaiproject.tool.api.ActiveTool;
import org.sakaiproject.tool.cover.ActiveToolManager;
import org.sakaiproject.site.cover.SiteService;
import org.sakaiproject.component.cover.ServerConfigurationService;
import org.sakaiproject.component.cover.ComponentManager;
import org.sakaiproject.lessonbuildertool.service.LessonSubmission;
import org.sakaiproject.db.cover.SqlService;
import org.sakaiproject.db.api.SqlReader;
import org.sakaiproject.tool.api.ToolManager;
import java.sql.Connection;
import java.sql.ResultSet;
import uk.org.ponder.messageutil.MessageLocator;
/**
* Interface to JForums, an optional forums system from Foothills
*
* @author Charles Hedrick <hedrick@rutgers.edu>
*
*/
// NOTE: almost no other class should import this. We want to be able
// to support both forums and jforum. So typically there will be a
// forumEntity, but it's injected, and it can be either forum and jforum.
// Hence it has to be declared LessonEntity. That leads to a lot of
// declarations like LessonEntity forumEntity. In this case forumEntity
// means either a ForumEntity or a JForumEntity. We can't just call the
// variables lessonEntity because the same module will probably have an
// injected class to handle tests and quizes as well. That will eventually
// be converted to be a LessonEntity.
public class JForumEntity implements LessonEntity, ForumInterface {
static boolean initdone = false;
static boolean haveJforum = false;
public void init() {
// they changed the capitalization, so check for both
if (ComponentManager.get("org.etudes.api.app.jforum.JforumService") != null ||
ComponentManager.get("org.etudes.api.app.jforum.JForumService") != null)
haveJforum = true;
System.out.println("JforumEntity init: haveJforum = " + haveJforum);
}
// to create bean. the bean is used only to call the pseudo-static
// methods such as getEntitiesInSite. So type, id, etc are left uninitialized
// static, because only the bean configured in applicationContext get these set
private static LessonEntity nextEntity = null;
public void setNextEntity(LessonEntity e) {
nextEntity = e;
}
public LessonEntity getNextEntity() {
return nextEntity;
}
private static ToolManager toolManager = null;
public void setToolManager(ToolManager t) {
toolManager = t;
}
static MessageLocator messageLocator = null;
public void setMessageLocator(MessageLocator m) {
messageLocator = m;
}
protected JForumEntity() {
}
protected JForumEntity(int type, int id, int level) {
this.type = type;
this.id = id;
this.level = level;
}
protected JForumEntity(int type, int id, int level, String name) {
this.type = type;
this.id = id;
this.level = level;
this.name = name;
}
public String getToolId() {
return "sakai.jforum.tool";
}
// standard info about object
protected int id;
protected int type;
protected int level;
protected String name = null;
protected String url = null;
// type of the underlying object
public int getType() {
return type;
}
public int getLevel() {
return level;
}
public int getTypeOfGrade() {
return 1;
}
public boolean isUsable() {
if (type == TYPE_JFORUM_TOPIC)
return true;
else
return false;
}
public String getReference() {
if (type == TYPE_JFORUM_TOPIC)
return "/" + JFORUM_TOPIC + "/" + id;
else if (type == TYPE_JFORUM_CATEGORY)
return "/" + JFORUM_CATEGORY + "/" + id;
else
return "/" + JFORUM_FORUM + "/" + id;
}
public List<LessonEntity> getEntitiesInSite() {
return getEntitiesInSite(null);
}
// find topics in site, but organized by forum and category
public List<LessonEntity> getEntitiesInSite(SimplePageBean bean) {
// all other code is driven by current objects. If we skip this code, nothing else
// in this module will be called.
if (!haveJforum) {
if (nextEntity != null)
return nextEntity.getEntitiesInSite();
else
return new ArrayList<LessonEntity>();
}
List<LessonEntity>ret = new ArrayList<LessonEntity>();
// LSNBLDR-21. If the tool is not in the current site we shouldn't query
// for topics owned by the tool.
String siteId = toolManager.getCurrentPlacement().getContext();
Site site = null;
try {
site = SiteService.getSite(siteId);
} catch (Exception impossible) {
return ret;
}
ToolConfiguration siteTool = site.getToolForCommonId("sakai.jforum.tool");
if(siteTool == null) {
// JForum is not in this site. Move on to the next provider.
if (nextEntity != null)
ret.addAll(nextEntity.getEntitiesInSite());
return ret;
}
String url = null;
try {
// String toolid = "8f83cd4b-74ca-4428-0055-85ddd19a8d00";
url = ServerConfigurationService.getToolUrl() + "/" + siteTool.getId() + "/posts/list/";
// String toolid = "8f83cd4b-74ca-4428-0055-85ddd19a8d00";
} catch (Exception e) {
System.out.println("tool problem " + e);
}
Connection connection = null;
try {
connection = SqlService.borrowConnection();
// jforum_sakai_course_categories: course_id, categories_id
String sql="select b.categories_id, b.title from jforum_sakai_course_categories a, jforum_categories b where a.course_id=? and a.categories_id = b.categories_id order by b.display_order";
Object fields[] = new Object[1];
fields[0] = siteId;
List<JForumEntity>categories = SqlService.dbRead(connection, sql, fields, new SqlReader()
{
public Object readSqlResultRecord(ResultSet result)
{
try {
return new JForumEntity(TYPE_JFORUM_CATEGORY, result.getInt(1), 1, result.getString(2));
} catch (Exception ignore) {};
return null;
}
});
if (categories != null && categories.size() > 0)
for (JForumEntity c : categories) {
boolean categoryAdded = false;
sql = "select forum_id,forum_name from jforum_forums where categories_id = ? order by forum_order";
fields[0] = c.id;
List<JForumEntity>forums = SqlService.dbRead(connection, sql, fields, new SqlReader()
{
public Object readSqlResultRecord(ResultSet result)
{
try {
return new JForumEntity(TYPE_JFORUM_FORUM, result.getInt(1), 1, result.getString(2));
} catch (Exception ignore) {};
return null;
}
});
if (forums != null && forums.size() > 0)
for (JForumEntity f : forums) {
sql = "select topic_id,topic_title from jforum_topics where forum_id = ? order by topic_time";
fields[0] = f.id;
List<JForumEntity>topics = SqlService.dbRead(connection, sql, fields, new SqlReader()
{
public Object readSqlResultRecord(ResultSet result)
{
try {
return new JForumEntity(TYPE_JFORUM_TOPIC, result.getInt(1), 2, result.getString(2));
} catch (Exception ignore) {};
return null;
}
});
if (topics != null && topics.size() > 0) {
if (!categoryAdded) {
ret.add(c);
categoryAdded = true;
}
ret.add(f);
for (JForumEntity t: topics) {
t.url = url + t.id + ".page";
ret.add(t);
}
}
}
}
} catch (Exception e) {
System.out.println("JForum Lesson Builder find all in site error " + e);
} finally {
try {
if (connection != null)
SqlService.returnConnection(connection);
} catch (Exception ignore) {};
}
if (nextEntity != null)
ret.addAll(nextEntity.getEntitiesInSite(bean));
return ret;
}
public LessonEntity getEntity(String ref, SimplePageBean o) {
return getEntity(ref);
}
public LessonEntity getEntity(String ref) {
int i = ref.indexOf("/",1);
if (i < 0)
return null;
String typeString = ref.substring(1, i);
String idString = ref.substring(i+1);
int id = 0;
if (typeString.equals(JFORUM_TOPIC) || typeString.equals(JFORUM_FORUM) || typeString.equals(JFORUM_CATEGORY)) {
try {
id = Integer.parseInt(idString);
} catch (Exception ignore) {
return null;
}
}
// note: I'm returning the minimal structures, not those with
// topics and postings attached
if (typeString.equals(JFORUM_TOPIC)) {
return new JForumEntity(TYPE_JFORUM_TOPIC, id, 2);
} else if (typeString.equals(JFORUM_FORUM)) {
return new JForumEntity(TYPE_JFORUM_FORUM, id, 1);
} else if (typeString.equals(JFORUM_CATEGORY)) {
return new JForumEntity(TYPE_JFORUM_CATEGORY, id, 1);
} else if (nextEntity != null) {
return nextEntity.getEntity(ref);
} else
return null;
}
// properties of entities
public String getTitle() {
if (name != null)
return name;
Connection connection = null;
try {
connection = SqlService.borrowConnection();
String sql = null;
if (type == TYPE_JFORUM_TOPIC)
sql = "select topic_title from jforum_topics where topic_id = ?";
else if (type == TYPE_JFORUM_CATEGORY)
sql = "select title from jforum_categories where categories_id =?";
else
sql = "select forum_name from jforum_forums where forum_id=?";
Object fields[] = new Object[1];
fields[0] = (Integer)id;
List<String>titles = SqlService.dbRead(connection, sql, fields, new SqlReader()
{
public Object readSqlResultRecord(ResultSet result)
{
try {
return result.getString(1);
} catch (Exception ignore) {};
return null;
}
});
if (titles != null && titles.size() > 0)
name = titles.get(0);
} catch (Exception e) {
System.out.println("JForum Lesson Builder get name error " + e);
} finally {
try {
if (connection != null)
SqlService.returnConnection(connection);
} catch (Exception ignore) {};
}
return name;
}
public String getUrl() {
if (url != null)
return url;
String siteId = toolManager.getCurrentPlacement().getContext();
Site site = null;
try {
site = SiteService.getSite(siteId);
} catch (Exception impossible) {
return null;
}
ToolConfiguration siteTool = site.getToolForCommonId("sakai.jforum.tool");
// LSNBLDR-21. If the tool is not in the current site we shouldn't return a url
if(siteTool == null) {
return null;
}
String prefix = null;
try {
// String toolid = "8f83cd4b-74ca-4428-0055-85ddd19a8d00";
prefix = ServerConfigurationService.getToolUrl() + "/" + siteTool.getId();
// String toolid = "8f83cd4b-74ca-4428-0055-85ddd19a8d00";
} catch (Exception e) {
System.out.println("tool problem " + e);
return null;
}
if (type == TYPE_JFORUM_TOPIC)
url = prefix + "/posts/list/" + id + ".page";
else if (type == TYPE_JFORUM_CATEGORY)
url = prefix + "/forums/list.page"; // no way to go directly to a category
else // forum
url = prefix + "/forums/show/" + id + ".page";
return url;
}
// I don't think they have this
public Date getDueDate() {
return null;
}
// the following methods all take references. So they're in effect static.
// They ignore the entity from which they're called.
// The reason for not making them a normal method is that many of the
// implementations seem to let you set access control and find submissions
// from a reference, without needing the actual object. So doing it this
// way could save some database activity
// access control
public boolean addEntityControl(String siteId, String groupId) throws IOException {
return false;
// not yet
};
public boolean removeEntityControl(String siteId, String groupId) throws IOException {
return false;
// not yet
};
// submission
// do we need the data from submission?
// not for the moment. If a posting is required, we just check whether one
// has been done. While you can grade submissions, grading is done manually
// later. It's unlikely that faculty will want to test on those grades
public boolean needSubmission(){
return false;
}
public LessonSubmission getSubmission(String user) {
return null; // not used
}
public int getSubmissionCount(String user) {
Connection connection = null;
try {
connection = SqlService.borrowConnection();
String sql = "select count(a.post_id) from jforum_posts a, jforum_users b where a.topic_id=? and b.sakai_user_id=? and a.user_id=b.user_id";
Object fields[] = new Object[2];
fields[0] = (Integer)id;
fields[1] = user;
List<Integer>counts = SqlService.dbRead(connection, sql, fields, new SqlReader()
{
public Object readSqlResultRecord(ResultSet result)
{
try {
return result.getInt(1);
} catch (Exception ignore) {};
return null;
}
});
if (counts != null && counts.size() > 0) {
return counts.get(0);
}
} catch (Exception e) {
System.out.println("JForum Lesson Builder get name error " + e);
} finally {
try {
if (connection != null)
SqlService.returnConnection(connection);
} catch (Exception ignore) {};
}
return 0;
}
// URL to create a new item. Normally called from the generic entity, not a specific one
// can't be null
public List<UrlItem> createNewUrls(SimplePageBean bean) {
ArrayList<UrlItem> list = new ArrayList<UrlItem>();
if (haveJforum) {
String tool = bean.getCurrentTool("sakai.jforum.tool");
if (tool != null) {
tool = ServerConfigurationService.getToolUrl() + "/" + tool + "/forums/list.page";
list.add(new UrlItem(tool, messageLocator.getMessage("simplepage.create_jforum")));
}
}
if (nextEntity != null)
list.addAll(nextEntity.createNewUrls(bean));
return list;
}
// URL to edit an existing entity.
// Can be null if we can't get one or it isn't needed
public String editItemUrl(SimplePageBean bean) {
return getUrl();
}
// for most entities editItem is enough, however tests allow separate editing of
// contents and settings. This will be null except in that situation
public String editItemSettingsUrl(SimplePageBean bean) {
return null;
}
public String importObject(String title, String topicTitle, String text, boolean texthtml, String base, String siteId, List<String>attachmenthrefs, boolean hide) {
return SimplePageItem.DUMMY;
}
public boolean objectExists() {
return getTitle() != null;
}
public boolean notPublished(String ref) {
return false;
}
// return the list of groups if the item is only accessible to specific groups
// null if it's accessible to the whole site.
public Collection<String> getGroups(boolean nocache) {
return null;
}
// set the item to be accessible only to the specific groups.
// null to make it accessible to the whole site
public void setGroups(Collection<String> groups) {
}
// WARNING:
// At least in the version of jforum we have, copying doesn't work. It copies only topics flagged as "reuse",
// but fails to copy the iitial posting. THat causes the UI to say the topic is not found. This code correctly
// copies the reference. But to test I had to hack the database to simulate a correct copying. Hopefully
// newer versions of jForum work.
class JForumTitle {
String topic;
String forum;
String category;
}
// only used for topics
public String getObjectId(){
String sql="select a.topic_title,b.forum_name,c.title from jforum_topics a,jforum_forums b,jforum_categories c where topic_id=? and b.forum_id=a.forum_id and b.categories_id=c.categories_id";
Object fields[] = new Object[1];
fields[0] = (Integer)id;
List<JForumTitle>titles = SqlService.dbRead(sql, fields, new SqlReader()
{
public Object readSqlResultRecord(ResultSet result)
{
try {
JForumTitle title = new JForumTitle();
title.topic = result.getString(1);
title.forum = result.getString(2);
title.category = result.getString(3);
return title;
} catch (Exception ignore) {};
return null;
}
});
if (titles.size() != 1)
return null;
JForumTitle title = titles.get(0);
// System.out.println("object " + "jforum_topic/" + title.category + "\n" + title.forum + "\n" + title.topic);
return "jforum_topic/" + title.category + "\n" + title.forum + "\n" + title.topic;
}
// objectid is titles of category, forum, topic. find the topic and return a string with its ID
public String findObject(String objectid, Map<String,String>objectMap, String siteid) {
// System.out.println("findobject " + objectid);
if (!haveJforum || !objectid.startsWith("jforum_topic/")) {
if (nextEntity != null)
return nextEntity.findObject(objectid, objectMap, siteid);
else
return null;
}
// LSNBLDR-21. If the tool is not in the current site we shouldn't query
// for topics owned by the tool.
Site site = null;
try {
site = SiteService.getSite(siteid);
} catch (Exception impossible) {
return null;
}
ToolConfiguration siteTool = site.getToolForCommonId("sakai.jforum.tool");
if(siteTool == null)
return null;
// isolate 3 titles
int i = objectid.indexOf("\n");
if (i <= 0)
return null;
final String category = objectid.substring("jforum_topic/".length(), i);
int j = objectid.indexOf("\n", i+1);
if (j <= 0)
return null;
final String forum = objectid.substring(i+1, j);
final String topic = objectid.substring(j+1);
// unfortunately we have to search the topic tree to find it.
// System.out.println("parsed " + category + ">" + forum +">" + topic);
List<LessonEntity>ret = new ArrayList<LessonEntity>();
String sql="select b.categories_id, b.title from jforum_sakai_course_categories a, jforum_categories b where a.course_id=? and a.categories_id = b.categories_id order by b.display_order";
Object fields[] = new Object[1];
fields[0] = siteid;
List<Integer>categories = SqlService.dbRead(sql, fields, new SqlReader()
{
public Object readSqlResultRecord(ResultSet result)
{
try {
if (result.getString(2).equals(category))
return result.getInt(1);
} catch (Exception ignore) {};
return null;
}
});
// System.out.println("found categories" + categories);
if (categories == null || categories.size() < 1)
return null;
List<Integer>forums = null;
// there will be only one non-null category id
for (Integer c : categories) {
if (c != null) {
sql = "select forum_id,forum_name from jforum_forums where categories_id = ? order by forum_order";
fields[0] = c;
forums = SqlService.dbRead(sql, fields, new SqlReader()
{
public Object readSqlResultRecord(ResultSet result)
{
try {
if (result.getString(2).equals(forum))
return result.getInt(1);
} catch (Exception ignore) {};
return null;
}
});
}
}
// System.out.println("found forums " + forums);
if (forums == null || forums.size() < 1)
return null;
List<Integer>topics = null;
// this will be only one non-null forum
for (Integer f : forums) {
if (f != null) {
sql = "select topic_id,topic_title from jforum_topics where forum_id = ? order by topic_time";
fields[0] = f;
topics = SqlService.dbRead(sql, fields, new SqlReader()
{
public Object readSqlResultRecord(ResultSet result)
{
try {
if (result.getString(2).equals(topic))
return result.getInt(1);
} catch (Exception ignore) {};
return null;
}
});
}
}
// System.out.println("topics " + topics);
if (topics == null || topics.size() < 1)
return null;
// there will be only one non-null topic
for (Integer t: topics) {
if (t != null) {
//System.out.println("return " + "/jforum_topic/" + t);
return "/jforum_topic/" + t;
}
}
return null;
}
public String getSiteId() {
if (type != TYPE_JFORUM_TOPIC)
return null;
String sql = "select c.course_id from jforum_topics a,jforum_forums b,jforum_sakai_course_categories c where a.topic_id = ? and a.forum_id=b.forum_id and b.categories_id=c.categories_id";
Object fields[] = new Object[1];
fields[0] = (Integer)id;
List<String>siteIds = SqlService.dbRead(sql, fields, null);
if (siteIds != null && siteIds.size() > 0)
return siteIds.get(0);
return null;
}
}