package org.sakaiproject.tool.syllabus.entityproviders;
import java.io.Serializable;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Calendar;
import java.util.Collections;
import java.util.Date;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
import lombok.Getter;
import lombok.Setter;
import lombok.extern.apachecommons.CommonsLog;
import org.apache.commons.lang.StringUtils;
import org.sakaiproject.api.app.syllabus.SyllabusAttachment;
import org.sakaiproject.api.app.syllabus.SyllabusData;
import org.sakaiproject.api.app.syllabus.SyllabusItem;
import org.sakaiproject.api.app.syllabus.SyllabusManager;
import org.sakaiproject.api.app.syllabus.SyllabusService;
import org.sakaiproject.content.api.ContentHostingService;
import org.sakaiproject.entitybroker.EntityReference;
import org.sakaiproject.entitybroker.EntityView;
import org.sakaiproject.entitybroker.entityprovider.EntityProvider;
import org.sakaiproject.entitybroker.entityprovider.annotations.EntityCustomAction;
import org.sakaiproject.entitybroker.entityprovider.capabilities.ActionsExecutable;
import org.sakaiproject.entitybroker.entityprovider.capabilities.AutoRegisterEntityProvider;
import org.sakaiproject.entitybroker.entityprovider.capabilities.DescribePropertiesable;
import org.sakaiproject.entitybroker.entityprovider.capabilities.Describeable;
import org.sakaiproject.entitybroker.entityprovider.capabilities.Outputable;
import org.sakaiproject.entitybroker.entityprovider.capabilities.RESTful;
import org.sakaiproject.entitybroker.entityprovider.extension.Formats;
import org.sakaiproject.entitybroker.entityprovider.search.Search;
import org.sakaiproject.entitybroker.exception.EntityNotFoundException;
import org.sakaiproject.entitybroker.util.AbstractEntityProvider;
import org.sakaiproject.exception.IdUnusedException;
import org.sakaiproject.exception.PermissionException;
import org.sakaiproject.site.api.Site;
import org.sakaiproject.site.api.SiteService;
import org.sakaiproject.site.api.ToolConfiguration;
import org.sakaiproject.tool.api.ToolManager;
/**
* Entity provider for the Syllabus tool
*/
@CommonsLog
public class SyllabusEntityProvider extends AbstractEntityProvider implements EntityProvider, AutoRegisterEntityProvider, ActionsExecutable, Outputable, Describeable, RESTful, DescribePropertiesable
{
public final static String ENTITY_PREFIX = "syllabus";
private final static SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm");
@Override
public String getEntityPrefix() {
return ENTITY_PREFIX;
}
/**
* site/siteId
*/
@EntityCustomAction(action = "site", viewKey = EntityView.VIEW_LIST)
public Syllabus getSyllabusForSite(EntityView view) {
// get siteId
String siteId = view.getPathSegment(2);
if(log.isDebugEnabled()) {
log.debug("news for site " + siteId);
}
// check siteId supplied
if (StringUtils.isBlank(siteId)) {
throw new IllegalArgumentException(
"siteId must be set in order to get the syllabus for a site, via the URL /syllabus/site/siteId");
}
//check user can access this site
Site site;
try {
site = siteService.getSiteVisit(siteId);
} catch (IdUnusedException e) {
throw new EntityNotFoundException("Invalid siteId: " + siteId, siteId);
} catch (PermissionException e) {
throw new EntityNotFoundException("No access to site: " + siteId, siteId);
}
//check user can access the tool, it might be hidden
ToolConfiguration toolConfig = site.getToolForCommonId("sakai.syllabus");
if(!toolManager.isVisible(site, toolConfig)) {
throw new EntityNotFoundException("No access to tool in site: " + siteId, siteId);
}
//get syllabus
SyllabusItem siteSyllabus = syllabusManager.getSyllabusItemByContextId(siteId);
if (siteSyllabus == null) {
throw new EntityNotFoundException("No syllabus for site: " + siteId, siteId);
}
//If its a redirect, return Syllabus with just the url set
Syllabus result = new Syllabus();
result.setSiteId(siteId);
if(StringUtils.isNotBlank(siteSyllabus.getRedirectURL())) {
result.setRedirectUrl(siteSyllabus.getRedirectURL());
return result;
}
//setup for checking items
boolean isMaintain = isMaintainer(siteId);
long currentTime = Calendar.getInstance().getTimeInMillis();
//Get the data
Set syllabusData = syllabusManager.getSyllabiForSyllabusItem(siteSyllabus);
List<Item> items = new ArrayList<Item>();
Iterator iter = syllabusData.iterator();
while(iter.hasNext()) {
SyllabusData sd = (SyllabusData)iter.next();
//Rule: if item is draft and not maintainer, skip it
if(StringUtils.equals(sd.getStatus(), SyllabusData.ITEM_DRAFT) && !isMaintain) {
continue;
}
//check dates are within range for normal users
long startDate = dateToLong(sd.getStartDate());
long endDate = dateToLong(sd.getEndDate());
//Rule: only check dates if not maintain
if(!isMaintain) {
//Rule: if we have a startDate and our currentTime is before it, then skip item
if(startDate > 0 && currentTime < startDate) {
continue;
}
//Rule: if we have an endDate and our currentTime is after it, then skip item
if(endDate > 0 && currentTime > endDate) {
continue;
}
}
//convert to our simplified object
Item item = new Item();
item.setTitle(sd.getTitle());
item.setData(sd.getAsset());
item.setOrder(sd.getPosition());
item.setStartDate(startDate);
item.setEndDate(endDate);
//get the attachments
List<Attachment> attachments = new ArrayList<Attachment>();
Set syllabusAttachments = syllabusManager.getSyllabusAttachmentsForSyllabusData(sd);
Iterator iter2 = syllabusAttachments.iterator();
while(iter2.hasNext()) {
SyllabusAttachment sa = (SyllabusAttachment)iter2.next();
Attachment a = new Attachment();
a.setTitle(sa.getName());
a.setUrl(sa.getUrl());
a.setType(sa.getType());
attachments.add(a);
}
item.setAttachments(attachments);
items.add(item);
}
result.setItems(items);
return result;
}
@Override
public String[] getHandledOutputFormats() {
return new String[] { Formats.XML, Formats.JSON};
}
@Setter
private SyllabusManager syllabusManager;
@Setter
private SiteService siteService;
@Setter
private ToolManager toolManager;
@Setter
private SyllabusService syllabusService;
@Setter
private ContentHostingService contentHostingService;
/**
* Simplified helper class to represent the Syllabus for a site
*/
public static class Syllabus {
@Getter @Setter
private String siteId;
@Getter @Setter
private String redirectUrl;
@Getter @Setter
private List<Item> items;
}
/**
* Simplified helper class to represent an individual syllabus item in a site
*/
public static class Item {
@Getter @Setter
private String title;
@Getter @Setter
private String data;
@Getter @Setter
private int order;
/* note thet EB turns these into millis anyway, so we might as well return them as millis */
@Getter @Setter
private long startDate;
@Getter @Setter
private long endDate;
@Getter @Setter
private List<Attachment> attachments;
}
/**
* Simplified helper class to store info about an attachment
*/
public static class Attachment {
@Getter @Setter
private String title;
@Getter @Setter
private String url;
@Getter @Setter
private String type;
}
//same logic as the tool uses
private boolean isMaintainer(String siteId) {
return siteService.allowUpdateSite(siteId);
}
//conert date to long milliseconds, nullsafe.
private long dateToLong(Date d) {
if(d != null) {
return d.getTime();
} else {
return 0;
}
}
public Object getEntity(EntityReference ref){
return new HashMap();
}
public void updateEntity(EntityReference ref, Object entity, Map<String, Object> params){
if(params != null){
if(params.containsKey("add") && params.containsKey("title") && params.containsKey("siteId")){
String siteId = (String) params.get("siteId");
if(!"".equals(siteId.trim())){
//check that this user is truly the maintainer
if(!isMaintainer(siteId)){
throw new IllegalArgumentException("User doesn't have access to modify this site.");
}
String title = (String) params.get("title");
title = title.trim();
if(!"".equals(title)){
SyllabusItem item = syllabusManager.getSyllabusItemByContextId(siteId);
int initPosition = syllabusManager.findLargestSyllabusPosition(item).intValue() + 1;
SyllabusData data = syllabusManager.createSyllabusDataObject(title, new Integer(initPosition), null, null, SyllabusData.ITEM_DRAFT, "none", null, null, Boolean.FALSE, null, null);
data.setView("no");
syllabusManager.addSyllabusToSyllabusItem(item, data);
}
}
}else{
//edit an existing item:
SyllabusData data = syllabusManager.getSyllabusData(ref.getId());
SyllabusItem item = syllabusManager.getSyllabusItem(data.getSyllabusItem().getSurrogateKey());
data.setSyllabusItem(item);
//now that we have an obj that has SiteId, let's verify that the user has access to modify it:
if(!isMaintainer(item.getContextId())){
throw new IllegalArgumentException("User doesn't have access to modify this site.");
}
if(params.containsKey("move")){
//this is a re-order update:
try{
int move = Integer.parseInt(params.get("move").toString());
boolean ascend = move > 0;
move = Math.abs(move);
if(data != null){
boolean foundItem = false;
int arrayCount = 0;
List<SyllabusData> movedData = new ArrayList<SyllabusData>();
String error = "";
Set syllabusData = syllabusManager.getSyllabiForSyllabusItem(item);
if(syllabusData != null){
for(SyllabusData d : (Set<SyllabusData>) syllabusData){
if(d.getSyllabusId().equals(data.getSyllabusId())){
foundItem = true;
if(ascend){
//we don't need to loop anymore
break;
}
}else{
if((ascend && !foundItem) || (!ascend && foundItem)){
movedData.add(d);
arrayCount++;
}
}
if(!ascend && arrayCount >= move){
break;
}
}
if("".equals(error)){
if(ascend){
//reverse the array if we are ascending
Collections.reverse(movedData);
}
int count = 0;
for (Iterator iterator = movedData.iterator(); iterator.hasNext();) {
SyllabusData d = (SyllabusData) iterator.next();
if(count < move){
//unfortunately, the positions aren't predictable like a simple pattern 1,2,3,4...
//but can be a random pattern like 1,4,5,6,9... This means we can only swap positions
Integer p1 = data.getPosition();
Integer p2 = d.getPosition();
data.setPosition(new Integer(p2));
d.setPosition(new Integer(p1));
}else{
//this isn't being moved
iterator.remove();
}
count++;
}
//now that the positions are swapped, store it:
for(SyllabusData d : movedData){
syllabusManager.updateSyllabudDataPosition(d, d.getPosition());
}
syllabusManager.updateSyllabudDataPosition(data, data.getPosition());
}else{
log.warn("Error while changing syllabus position: " + error);
}
}
}
}catch(Exception e){
log.warn("Move value wasn't a valid number: " + params.get("move"));
}
}else if(params.containsKey("name")){
if("title".equals(params.get("name"))){
String title = (String) params.get("value");
if(title != null && !"".equals(title.trim())){
data.setTitle(title.trim());
syllabusManager.saveSyllabus(data);
}
}else if("body".equals(params.get("name"))){
String body = (String) params.get("value");
data.setAsset(body);
syllabusManager.saveSyllabus(data);
}else if("startDate".equals(params.get("name"))){
String startDate = (String) params.get("value");
if(startDate == null || "".equals(startDate)){
data.setStartDate(null);
}else{
try{
Date sDate = dateFormat.parse(startDate);
if(data.getEndDate() == null || data.getEndDate().after(sDate)){
data.setStartDate(sDate);
}else{
throw new IllegalArgumentException("End Date must be after start date");
}
}catch(Exception e){
log.error(e.getMessage(), e);
throw new IllegalArgumentException("Date isn't in the correct format: " + startDate + ", should be: yyyy-MM-dd HH:mm");
}
}
syllabusManager.saveSyllabus(data);
}else if("endDate".equals(params.get("name"))){
String endDate = (String) params.get("value");
if(endDate == null || "".equals(endDate)){
data.setEndDate(null);
}else{
try {
Date eDate = dateFormat.parse(endDate);
if(data.getStartDate() == null || eDate.after(data.getStartDate())){
data.setEndDate(eDate);
}else{
throw new IllegalArgumentException("End Date must be after start date");
}
} catch (Exception e) {
log.error(e.getMessage(), e);
throw new IllegalArgumentException("Date isn't in the correct format: " + endDate + ", should be: yyyy-MM-dd HH:mm");
}
}
syllabusManager.saveSyllabus(data);
}
}else if(params.containsKey("toggle") && params.containsKey("status")){
String statusStr = (String) params.get("status");
boolean status = false;
if("true".equals(statusStr)){
status = true;
}
if("publish".equals(params.get("toggle"))){
if(status){
data.setStatus(SyllabusData.ITEM_POSTED);
}else{
data.setStatus(SyllabusData.ITEM_DRAFT);
}
syllabusManager.saveSyllabus(data);
syllabusService.draftChangeSyllabus(data, item.getContextId());
}else if("linkCalendar".equals(params.get("toggle"))){
if(status){
data.setLinkCalendar(true);
}else{
data.setLinkCalendar(false);
}
syllabusManager.saveSyllabus(data);
}
}else if(params.containsKey("delete")){
//Delete item
syllabusService.deletePostedSyllabus(data, item.getContextId());
Set syllabusAttachments = syllabusManager.getSyllabusAttachmentsForSyllabusData(data);
Iterator iter = syllabusAttachments.iterator();
while(iter.hasNext())
{
SyllabusAttachment attach = (SyllabusAttachment)iter.next();
String id = attach.getAttachmentId();
syllabusManager.removeSyllabusAttachSyllabusData(data, attach);
if(id.toLowerCase().startsWith("/attachment"))
try{
contentHostingService.removeResource(id);
}catch(Exception e){
log.error(e.getMessage(), e);
}
}
syllabusManager.removeCalendarEvents(data);
syllabusManager.removeSyllabusFromSyllabusItem(item, data);
}else if(params.containsKey("deleteAttachment") && params.containsKey("attachmentId")){
//delete attachment
String attachmentIdStr = (String) params.get("attachmentId");
if(!"".equals(attachmentIdStr.trim())){
SyllabusAttachment attachment = syllabusManager.getSyllabusAttachment(attachmentIdStr);
syllabusManager.removeSyllabusAttachSyllabusData(data, attachment);
//update calendar attachments
if(data.getCalendarEventIdStartDate() != null
&& !"".equals(data.getCalendarEventIdStartDate())){
syllabusManager.removeCalendarAttachments(item.getContextId(), data.getCalendarEventIdStartDate(), attachment);
}
if(data.getCalendarEventIdEndDate() != null
&& !"".equals(data.getCalendarEventIdEndDate())){
syllabusManager.removeCalendarAttachments(item.getContextId(), data.getCalendarEventIdEndDate(), attachment);
}
if(attachmentIdStr.toLowerCase().startsWith("/attachment")){
try{
contentHostingService.removeResource(attachmentIdStr);
}catch(Exception e){
log.error(e.getMessage(), e);
}
}
}
}
}
}
}
public String createEntity(EntityReference ref, Object entity, Map<String, Object> params){
return null;
}
public void deleteEntity(EntityReference ref, Map<String, Object> params){
}
public Object getSampleEntity(){
return new HashMap();
}
public List<?> getEntities(EntityReference ref, Search search){
return null;
}
public String[] getHandledInputFormats(){
return new String[] { Formats.XML, Formats.JSON, Formats.HTML};
}
/**
* The following two methods are so we can have our properties file nested in sub directories that the current syllabus bundle already provides
* @return
*/
public String getBaseName() {
return "org/sakaiproject/tool/syllabus/bundle/syllabus";
}
public ClassLoader getResourceClassLoader() {
return SyllabusEntityProvider.class.getClassLoader();
}
}