/**********************************************************************************
* $URL: https://source.sakaiproject.org/contrib/conditionalrelease/tags/sakai_2-4-1/impl/src/java/org/sakaiproject/conditions/impl/ResourceReleaseRule.java $
* $Id: ResourceReleaseRule.java 44304 2007-12-17 04:35:22Z zach.thomas@txstate.edu $
***********************************************************************************
*
* Copyright (c) 2003, 2004, 2005, 2006, 2007, 2008 Sakai Foundation
*
* 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.conditions.impl;
import java.util.*;
import org.apache.commons.collections.Predicate;
import org.apache.commons.collections.PredicateUtils;
import org.sakaiproject.authz.api.AuthzGroup;
import org.sakaiproject.authz.api.GroupNotDefinedException;
import org.sakaiproject.authz.api.Member;
import org.sakaiproject.authz.api.SecurityAdvisor;
import org.sakaiproject.authz.api.AuthzGroupService;
import org.sakaiproject.authz.api.SecurityService;
import org.sakaiproject.component.cover.ComponentManager;
import org.sakaiproject.conditions.api.Condition;
import org.sakaiproject.conditions.api.ConditionService;
import org.sakaiproject.conditions.api.Rule;
import org.sakaiproject.content.api.ContentCollectionEdit;
import org.sakaiproject.content.api.ContentResourceEdit;
import org.sakaiproject.content.api.GroupAwareEdit;
import org.sakaiproject.content.api.ContentHostingService;
import org.sakaiproject.entity.api.ResourceProperties;
import org.sakaiproject.event.api.Event;
import org.sakaiproject.event.api.Notification;
import org.sakaiproject.event.api.NotificationAction;
import org.sakaiproject.event.api.NotificationService;
import org.sakaiproject.event.api.Obsoletable;
import org.sakaiproject.exception.IdUnusedException;
import org.sakaiproject.exception.InUseException;
import org.sakaiproject.exception.OverQuotaException;
import org.sakaiproject.exception.PermissionException;
import org.sakaiproject.exception.ServerOverloadException;
import org.sakaiproject.exception.TypeException;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
/**
* @author zach
*
*/
public class ResourceReleaseRule implements Rule, Obsoletable {
private static final String SATISFIES_RULE = "resource.satisfies.rule";
private static final long LENGTH_OF_A_DAY = 86400000; // length of a day in milliseconds
private String resourceId;
private List<Condition> predicates;
private Conjunction conj;
private ContentHostingService chs = (ContentHostingService)ComponentManager.get("org.sakaiproject.content.api.ContentHostingService");
public void setContentHostingService(ContentHostingService chs) {
this.chs = chs;
}
private ConditionService conditionService = (ConditionService) ComponentManager.get("org.sakaiproject.conditions.api.ConditionService");
public void setConditionService(ConditionService conditionService) {
this.conditionService = conditionService;
}
private SecurityService securityService = (SecurityService)ComponentManager.get("org.sakaiproject.authz.api.SecurityService");
public void setSecurityService(SecurityService securityService) {
this.securityService = securityService;
}
private AuthzGroupService authzGroupService = (AuthzGroupService)ComponentManager.get("org.sakaiproject.authz.api.AuthzGroupService");;
public void setAuthzGroupService(AuthzGroupService authzGroupService) {
this.authzGroupService = authzGroupService;
}
// we need a no-arg constructor so BaseNotificationService can instantiate these things with Class.forName(className).newInstance();
public ResourceReleaseRule() {
}
public ResourceReleaseRule(String resourceId, List<Condition> predicates, Conjunction conj) {
this.resourceId = resourceId;
this.predicates = predicates;
this.conj = conj;
}
/* (non-Javadoc)
* @see org.apache.commons.collections.Predicate#evaluate(java.lang.Object)
*/
public boolean evaluate(Object arg0) {
Predicate judgement = new NullPredicate();
if (predicates.size() == 1) {
judgement = predicates.get(0);
} else {
if (conj == Conjunction.AND) {
judgement = PredicateUtils.allPredicate(predicates);
}
else if (conj == Conjunction.OR) {
judgement = PredicateUtils.anyPredicate(predicates);
}
}
return judgement.evaluate(arg0);
}
/* (non-Javadoc)
* @see org.sakaiproject.event.api.NotificationAction#getClone()
*/
public NotificationAction getClone() {
// TODO Auto-generated method stub
return null;
}
/* (non-Javadoc)
* @see org.sakaiproject.event.api.NotificationAction#notify(org.sakaiproject.event.api.Notification, org.sakaiproject.event.api.Event)
*/
public void notify(Notification notification, Event event) {
securityService.pushAdvisor(new SecurityAdvisor() {
public SecurityAdvice isAllowed(String userId, String function,
String reference) {
return SecurityAdvice.ALLOWED;
}
});
if (this.isObsolete()) return;
if (("gradebook.updateItemScore").equals(event.getEvent())) {
AssignmentGrading grading = produceAssignmentGradingFromEvent(event);
boolean shouldBeAvailable = this.evaluate(grading);
// update access control list on ContentHostingService here
try {
GroupAwareEdit resource = null;
if (chs.isCollection(this.resourceId)) {
resource = chs.editCollection(this.resourceId);
} else {
resource = chs.editResource(this.resourceId);
}
ResourceProperties resourceProps = resource.getProperties();
// since we're following a per-user event now, the global rule property should be removed
resourceProps.removeProperty(SATISFIES_RULE);
List<String> prop = resourceProps.getPropertyList(ContentHostingService.CONDITIONAL_ACCESS_LIST);
if (prop == null) prop = new ArrayList<String>();
Set<String> acl = new TreeSet<String>(prop);
if ((shouldBeAvailable && acl.contains(grading.getUserId()))
|| (!shouldBeAvailable && !acl.contains(grading.getUserId()))) {
// no change to the ACL necessary, but we still have to commit the change to SATISFIES_RULE
if (chs.isCollection(this.resourceId)) {
chs.commitCollection((ContentCollectionEdit)resource);
} else {
chs.commitResource((ContentResourceEdit)resource, NotificationService.NOTI_NONE);
}
securityService.popAdvisor();
return;
}
if (!shouldBeAvailable && acl.contains(grading.getUserId())) {
// remove user from access list
acl.remove(grading.getUserId());
// time to re-populate the property list, start by tearing it down
// we only have to do it this way because props does not have a removePropertyFromList method
resourceProps.removeProperty(ContentHostingService.CONDITIONAL_ACCESS_LIST);
for (String id : acl) {
resourceProps.addPropertyToList(ContentHostingService.CONDITIONAL_ACCESS_LIST, id);
}
} else if (shouldBeAvailable && !acl.contains(grading.getUserId())) {
// add user to access list
resourceProps.addPropertyToList(ContentHostingService.CONDITIONAL_ACCESS_LIST, grading.getUserId());
}
if (chs.isCollection(this.resourceId)) {
chs.commitCollection((ContentCollectionEdit)resource);
} else {
chs.commitResource((ContentResourceEdit)resource, NotificationService.NOTI_NONE);
}
} catch (PermissionException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (IdUnusedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (TypeException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (InUseException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (OverQuotaException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (ServerOverloadException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} finally {
securityService.popAdvisor();
}
} else if ("gradebook.updateAssignment".equals(event.getEvent()) || ("cond+gradebook.updateAssignment").equals(event.getEvent()) || ("datetime.update".equals(event.getEvent()))) {
// this availability applies to the whole Resource, not on a per-user basis
// TODO set the resource availability
// foo bar baz
AssignmentUpdate update = produceAssignmentUpdateFromEvent(event);
boolean shouldBeAvailable = this.evaluate(update);
try {
GroupAwareEdit resource = null;
if (chs.isCollection(this.resourceId)) {
resource = chs.editCollection(this.resourceId);
} else {
resource = chs.editResource(this.resourceId);
}
ResourceProperties resourceProps = resource.getProperties();
resourceProps.addProperty(SATISFIES_RULE, Boolean.valueOf(shouldBeAvailable).toString());
if (chs.isCollection(this.resourceId)) {
chs.commitCollection((ContentCollectionEdit)resource);
} else {
chs.commitResource((ContentResourceEdit)resource, NotificationService.NOTI_NONE);
}
} catch (PermissionException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (IdUnusedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (TypeException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (InUseException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (OverQuotaException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (ServerOverloadException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} finally {
securityService.popAdvisor();
}
} else if (("cond+gradebook.updateItemScore").equals(event.getEvent())) {
// this event means the Rule has just been added
// and we need to look at any scores that may have been recorded already
try {
String[] assignmentRefParts = event.getResource().split("/");
String[] resourceRefParts = this.resourceId.split("/");
String authzRef = "/site/" + resourceRefParts[2];
AuthzGroup group = authzGroupService.getAuthzGroup(authzRef);
Set<Member> members = group.getMembers();
// build access control list up from scratch using site members
Set<String> acl = new HashSet<String>();
for (Member member : members) {
boolean shouldBeAvailable = false;
if (member.getRole().getId().equals(group.getMaintainRole())) {
// we don't bother putting maintainers in the ACL
continue;
} else {
Map<String,String> scoreData = conditionService.getConditionProvider("gradebook").getData("grades", assignmentRefParts[2] + "|" + assignmentRefParts[3] + "|" + member.getUserId());
String scoreString = scoreData.get("score");
Double score;
try {
score = Double.parseDouble(scoreString);
} catch (NumberFormatException e) {
score = null;
}
AssignmentGrading grading = produceAssignmentGrading(member.getUserId(), score);
shouldBeAvailable = this.evaluate(grading);
}
if (shouldBeAvailable) acl.add(member.getUserId());
}
// update state on this resource
GroupAwareEdit resource = null;
if (chs.isCollection(this.resourceId)) {
resource = chs.editCollection(this.resourceId);
} else {
resource = chs.editResource(this.resourceId);
}
ResourceProperties resourceProps = resource.getProperties();
// since we're following a per-user event now, the global rule property should be removed
resourceProps.removeProperty(SATISFIES_RULE);
resourceProps.removeProperty(ContentHostingService.CONDITIONAL_ACCESS_LIST);
for (String id : acl) {
resourceProps.addPropertyToList(ContentHostingService.CONDITIONAL_ACCESS_LIST, id);
}
if (chs.isCollection(this.resourceId)) {
chs.commitCollection((ContentCollectionEdit)resource);
} else {
chs.commitResource((ContentResourceEdit)resource, NotificationService.NOTI_NONE);
}
} catch (GroupNotDefinedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (IdUnusedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (TypeException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (PermissionException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (InUseException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (OverQuotaException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (ServerOverloadException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} finally {
securityService.popAdvisor();
}
}
}
public boolean isObsolete() {
securityService.pushAdvisor(new SecurityAdvisor() {
public SecurityAdvice isAllowed(String userId, String function,
String reference) {
return SecurityAdvice.ALLOWED;
}
});
try {
chs.getProperties(this.resourceId);
return false;
} catch (PermissionException e1) {
return true;
} catch (IdUnusedException e1) {
return true;
} finally {
securityService.popAdvisor();
}
}
private AssignmentUpdate produceAssignmentUpdateFromEvent(Event event) {
// event resource of the form: /gradebook/[gradebook id]/[assignment name]/[points possible]/[due date millis]/[is released]/[is included in course grade]/[has authz]
AssignmentUpdate rv = new AssignmentUpdate();
if ("datetime.update".equals(event.getEvent())) {
for (Predicate p : this.predicates) {
if (((Condition)p).getArgument() != null) {
Object arg = ((Condition)p).getArgument();
if ((arg instanceof String) && (((String)arg).startsWith("dateMillis:"))) {
rv.setDueDate(new java.util.Date(Long.parseLong(((String)((Condition)p).getArgument()).substring("dateMillis:".length()))));
}
return rv;
}
}
return rv;
}
String[] assignmentRefParts = event.getResource().split("/");
rv.setTitle(assignmentRefParts[3]);
rv.setDueDate(new Date(Long.parseLong(assignmentRefParts[5])));
rv.setIncludedInCourseGrade(Boolean.parseBoolean(assignmentRefParts[7]));
rv.setReleasedToStudents(Boolean.parseBoolean(assignmentRefParts[6]));
// since we've received an update, we'd better update the predicates
for (Predicate p : this.predicates) {
if (((Condition)p).getArgument() != null) {
Object arg = ((Condition)p).getArgument();
if ((arg instanceof String) && (((String)arg).startsWith("dateMillis:"))) {
((BooleanExpression)p).setArgument("dateMillis:" + rv.getDueDate().getTime());
}
}
}
return rv;
}
private Date addADay(Date date) {
return (date == null) ? null : new java.util.Date(date.getTime() + LENGTH_OF_A_DAY);
}
/* (non-Javadoc)
* @see org.sakaiproject.event.api.NotificationAction#set(org.w3c.dom.Element)
*/
public void set(Element el) {
// setup for predicates
predicates = new Vector();
this.resourceId = el.getAttribute("resourceId");
String conjunction = el.getAttribute("conjunction");
if("OR".equals(conjunction)) {
this.conj = Rule.Conjunction.OR;
} else if("AND".equals(conjunction)) {
this.conj = Rule.Conjunction.AND;
}
// the children (predicates)
NodeList children = el.getChildNodes();
final int length = children.getLength();
for (int i = 0; i < length; i++)
{
Node child = children.item(i);
if (child.getNodeType() != Node.ELEMENT_NODE) continue;
Element element = (Element) child;
// look for properties
if (element.getTagName().equals("predicates"))
{
// re-create properties
predicates = reconstitutePredicates(element);
}
}
}
private List<Condition> reconstitutePredicates(Element element) {
List<Condition> rv = new ArrayList<Condition>();
try {
Condition aPredicate = null;
NodeList children = element.getChildNodes();
final int length = children.getLength();
for (int i = 0; i < length; i++) {
Node child = children.item(i);
if (child.getNodeType() != Node.ELEMENT_NODE) continue;
Element predicate = (Element) child;
// look for properties
if (predicate.getTagName().equals("predicate")) {
String className = predicate.getAttribute("class");
aPredicate = (Condition) Class.forName(className).newInstance();
((BooleanExpression) aPredicate).setReceiver(predicate.getAttribute("receiver"));
((BooleanExpression) aPredicate).setMethod(predicate.getAttribute("method"));
((BooleanExpression) aPredicate).setOperator(predicate.getAttribute("operator"));
((BooleanExpression) aPredicate).setArgument(predicate.getAttribute("argument"));
rv.add(aPredicate);
}
}
} catch (InstantiationException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (IllegalAccessException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (ClassNotFoundException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
return rv;
}
/* (non-Javadoc)
* @see org.sakaiproject.event.api.NotificationAction#set(org.sakaiproject.event.api.NotificationAction)
*/
public void set(NotificationAction other) {
ResourceReleaseRule eOther = (ResourceReleaseRule) other;
resourceId = eOther.resourceId;
}
/* (non-Javadoc)
* @see org.sakaiproject.event.api.NotificationAction#toXml(org.w3c.dom.Element)
*/
public void toXml(Element el) {
el.setAttribute("resourceId", this.resourceId);
if(this.conj == Rule.Conjunction.OR) {
el.setAttribute("conjunction", "OR");
} else if(this.conj == Rule.Conjunction.AND) {
el.setAttribute("conjunction", "AND");
}
Element predicates = el.getOwnerDocument().createElement("predicates");
el.appendChild(predicates);
for (Predicate p : this.predicates) {
Element predicateElement = el.getOwnerDocument().createElement("predicate");
predicateElement.setAttribute("class", p.getClass().getName());
predicateElement.setAttribute("receiver", ((BooleanExpression)p).getReceiver());
predicateElement.setAttribute("method", ((BooleanExpression)p).getMethod());
predicateElement.setAttribute("operator", ((BooleanExpression)p).getOperator());
Object argument = ((BooleanExpression)p).getArgument();
if (argument == null) {
argument = "";
}
predicateElement.setAttribute("argument", argument.toString());
predicates.appendChild(predicateElement);
}
}
private AssignmentGrading produceAssignmentGradingFromEvent(Event event) {
Double score;
String userId;
String[] assignmentRefParts = event.getResource().split("/");
// a score may be null after a grading event
try {
score = new Double(assignmentRefParts[5]);
} catch (NumberFormatException e) {
score = null;
}
userId = assignmentRefParts[4];
return produceAssignmentGrading(userId, score);
}
private AssignmentGrading produceAssignmentGrading(String userId, Double score) {
AssignmentGrading rv = new AssignmentGrading();
rv.setUserId(userId);
rv.setScore(score);
return rv;
}
}