Skip to content

Commit

Permalink
SAK-30075 - Web service for finding and fixing bad attachments in Sam…
Browse files Browse the repository at this point in the history
…igo pools
  • Loading branch information
danielmerino committed Nov 26, 2015
1 parent 0f0563b commit 0434f4d
Show file tree
Hide file tree
Showing 9 changed files with 410 additions and 1 deletion.
Original file line number Diff line number Diff line change
Expand Up @@ -135,5 +135,7 @@ public void copyPool(Tree tree, String agentId, Long sourceId,
public QuestionPoolDataIfc savePool(QuestionPoolDataIfc pool);

public Map getQuestionPoolItemMap();

public String getUserPoolAttachmentReport(String userId, Long poolId, String contextToReplace);

}
4 changes: 4 additions & 0 deletions samigo/samigo-pack/src/webapp/WEB-INF/components.xml
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,10 @@
<bean id="org_sakaiproject_tool_assessment_services_samlite_SamLiteService"
class="org.sakaiproject.tool.assessment.samlite.impl.SamLiteServiceImpl"
init-method="init"/>

<bean id="org.sakaiproject.tool.assessment.shared.api.questionpool.QuestionPoolServiceAPI"
class="org.sakaiproject.tool.assessment.shared.impl.questionpool.QuestionPoolServiceImpl"
singleton="true"/>

<bean id="org.sakaiproject.springframework.orm.hibernate.impl.AdditionalHibernateMappingsImpl.samigo"
class="org.sakaiproject.springframework.orm.hibernate.impl.AdditionalHibernateMappingsImpl">
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,11 @@
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.net.URLDecoder;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Set;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
Expand All @@ -37,6 +42,21 @@
import org.sakaiproject.tool.assessment.shared.api.questionpool.QuestionPoolServiceAPI;
import org.sakaiproject.tool.assessment.services.QuestionPoolServiceException;

import org.sakaiproject.content.api.ContentResource;
import org.apache.commons.lang.StringUtils;
import org.sakaiproject.exception.IdUnusedException;
import org.sakaiproject.exception.PermissionException;
import org.sakaiproject.exception.TypeException;
import org.sakaiproject.authz.api.SecurityAdvisor;
import org.sakaiproject.authz.api.SecurityAdvisor.SecurityAdvice;
import org.sakaiproject.authz.cover.SecurityService;
import org.sakaiproject.tool.assessment.services.assessment.AssessmentService;
import org.sakaiproject.tool.assessment.data.ifc.assessment.ItemAttachmentIfc;
import org.sakaiproject.tool.assessment.data.dao.assessment.Answer;
import org.sakaiproject.tool.assessment.data.dao.assessment.ItemText;
import org.sakaiproject.tool.assessment.services.ItemService;
import org.sakaiproject.tool.assessment.facade.ItemFacade;

/**
*
* The QuestionPoolServiceAPI declares a shared interface to control question
Expand Down Expand Up @@ -393,4 +413,219 @@ public Map getQuestionPoolItemMap()
}
}

final class UserPoolAttachmentReport
{
private final StringBuilder report;

public UserPoolAttachmentReport()
{
this.report = new StringBuilder();
}

public String getReport() {
return report.toString();
}

private void addToReport(String info)
{
this.report.append(info);
}

public String findAttachmentsInText(String text, String contextToReplace)
{
String replacedAttachment;

if(text != null)
{
String[] sources = StringUtils.splitByWholeSeparator(text, "src=\"");

Set<String> attachments = new HashSet<String>();
for (String source : sources)
{
String theHref = StringUtils.substringBefore(source, "\"");
if (StringUtils.contains(theHref, "/access/content/"))
{
attachments.add(theHref);
}
}
if (attachments.size() > 0)
{
addToReport("\nFound " + attachments.size() + " attachments buried in question or answer text.\n\n");

for (String attachment: attachments)
{
replacedAttachment=replaceAttachment(attachment, contextToReplace);
if ((!replacedAttachment.equals(attachment))&&(contextToReplace!=null))
{
text = StringUtils.replace(text, attachment, replacedAttachment);
}
}
}
}
return text;
}

private String replaceAttachment(String attachment, String contextToReplace)
{
ContentResource cr = null;

String resourceIdOrig = "/" + StringUtils.substringAfter(attachment, "/access/content/");
String resourceId = URLDecoder.decode(resourceIdOrig);
String filename = StringUtils.substringAfterLast(attachment, "/");

try
{
cr = AssessmentService.getContentHostingService().getResource(resourceId);
}
catch (IdUnusedException e)
{
addToReport("\nCould not find attachment (" + resourceId + ").\n\n");
}
catch (TypeException e)
{
addToReport("\nTypeException for resource (" + resourceId + ") that was embedded in a question or answer.\n\n");
}
catch (PermissionException e)
{
addToReport("\nNo permission for attachment (" + resourceId + ").\n\n");

//If resource exists but user has not access to it, make a copy of the resource in the new accessible context.
if ((contextToReplace!=null) && StringUtils.isNotEmpty(filename))
{
//Overriding current user's permissions to make a copy of the resource.
SecurityService.pushAdvisor(new SecurityAdvisor(){
@Override
public SecurityAdvice isAllowed(String arg0, String arg1,
String arg2) {
if("content.read".equals(arg1)){
return SecurityAdvice.ALLOWED;
}else{
return SecurityAdvice.PASS;
}
}
});

try
{
cr = AssessmentService.getContentHostingService().getResource(resourceId);

ContentResource crCopy = new AssessmentService().createCopyOfContentResource(cr.getId(), filename, contextToReplace);
//getUrl respects non-ascii chars, getReference does not.
attachment = StringUtils.replace(attachment, resourceIdOrig, StringUtils.substringAfter(crCopy.getUrl(), "/content"));
addToReport("\nCopied unusable attachment to new context resources folder: "+attachment+" .\n\n");
}
catch(Exception e2)
{
addToReport("\nCould NOT copy old attachment "+attachment+" to new attachment in site "+contextToReplace+" .\n\n");
e2.printStackTrace();
}
}

SecurityService.popAdvisor();
}
return attachment;
}
}

public String getUserPoolAttachmentReport(String userId, Long poolId, String contextToReplace)
{
String parsedText = null;
UserPoolAttachmentReport upar = new UserPoolAttachmentReport();

boolean flagQuestionPoolUpdated=false;
boolean flagTextUpdated = false;
boolean flagAttachmentUpdated = false;
boolean flagAnswerUpdated = false;

ItemService itemService = new ItemService();

//Get user's pool with questions.
//QuestionPoolFacade qpf = this.getPool(poolId, userId);
QuestionPoolDataIfc qpf = this.getPool(poolId, userId);

if (qpf==null)
{
upar.addToReport("POOL ID: "+poolId+" NOT FOUND IN USER "+userId+".");
return upar.toString();
}
upar.addToReport("POOL ---> "+qpf.getTitle()+" - POOL ID: "+qpf.getQuestionPoolId()+"\n\n");

Iterator iter = this.getAllItems(poolId).iterator();

while (iter.hasNext())
{
ItemFacade itemData = (ItemFacade) iter.next();

//Parsing the question text looking for embedded attachments.
Set misItemText = itemData.getItemTextSet();

HashSet newItemTextSet = new HashSet(); //Modified question text.
HashSet newAnswerSet = new HashSet(); //Modified answers text.

Iterator itemTextIter = misItemText.iterator();
while (itemTextIter.hasNext())
{
//Looking for bad attachments in question text.
ItemText iti = (ItemText) itemTextIter.next();
upar.addToReport("Question Text ---> "+iti.getText()+"\n");

parsedText = upar.findAttachmentsInText(iti.getText(), contextToReplace);
if (!parsedText.equals(iti.getText()))
{
flagTextUpdated=true;
iti.setText(parsedText);
}

//Looking for bad attachments in question text.
Set myAnswerSet = iti.getAnswerSet();
Iterator answerIter = myAnswerSet.iterator();
while (answerIter.hasNext())
{
Answer myAnswer = (Answer) answerIter.next();
parsedText = upar.findAttachmentsInText(myAnswer.getText(), contextToReplace);
if (!parsedText.equals(myAnswer.getText()))
{
flagAnswerUpdated=flagTextUpdated=true;
myAnswer.setText(parsedText);
}
newAnswerSet.add(myAnswer);
}

if (flagAnswerUpdated) iti.setAnswerSet(newAnswerSet);
flagAnswerUpdated=false;

newItemTextSet.add(iti);
}
if (flagTextUpdated) itemData.setItemTextSet(newItemTextSet);

//Looking for bad attachments in question's attachments (out of CKEditor).
StringBuilder uploadedAttachments = new StringBuilder();
ArrayList misAttachments = (ArrayList<ItemAttachmentIfc>) itemData.getItemAttachmentList();
if (misAttachments.size()>0) upar.addToReport("\nFound " + misAttachments.size() + " uploaded attachments in the question.\n\n");
for (int i=0;i<misAttachments.size();i++)
{
ItemAttachmentIfc ia = (ItemAttachmentIfc) misAttachments.get(i);
//uploadedAttachments.append("src=\""+ia.getLocation()+"\" ");
parsedText = upar.replaceAttachment(ia.getLocation(), contextToReplace);
if (!parsedText.equals(ia.getLocation()))
{
flagAttachmentUpdated=true;
ia.setLocation(parsedText);
misAttachments.set(i, ia);
}
}
if (flagAttachmentUpdated) itemData.setItemAttachmentSet(new HashSet(misAttachments));

if (flagTextUpdated || flagAttachmentUpdated)
{
flagQuestionPoolUpdated=true;
itemService.saveItem(itemData);
}

flagTextUpdated=false;
flagAttachmentUpdated=false;
}
if (flagQuestionPoolUpdated) this.savePool(qpf);
return upar.getReport();
}
}
10 changes: 10 additions & 0 deletions webservices/cxf/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -179,6 +179,16 @@
<version>2.0.31-beta</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>OKI</groupId>
<artifactId>OkiOSID</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>OKI</groupId>
<artifactId>OkiSID</artifactId>
<version>rc6.1</version>
</dependency>
</dependencies>

<build>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@
import org.sakaiproject.shortenedurl.api.ShortenedUrlService;
import org.sakaiproject.tool.assessment.samlite.api.SamLiteService;
import org.sakaiproject.id.api.IdManager;
import org.sakaiproject.tool.assessment.shared.api.questionpool.QuestionPoolServiceAPI;

import javax.jws.WebMethod;
import javax.jws.WebService;
Expand Down Expand Up @@ -86,6 +87,7 @@ public class AbstractWebService {
protected IdManager idManager;
protected GradebookExternalAssessmentService gradebookExternalAssessmentService;
protected ActivityService activityService;
protected QuestionPoolServiceAPI questionPoolServiceImpl;


@WebMethod(exclude = true)
Expand Down Expand Up @@ -260,4 +262,9 @@ public void setActivityService(ActivityService activityService) {
public void setTimeService(TimeService timeService) {
this.timeService = timeService;
}

@WebMethod(exclude = true)
public void setQuestionPoolServiceImpl(QuestionPoolServiceAPI questionPoolServiceImpl) {
this.questionPoolServiceImpl = questionPoolServiceImpl;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,9 @@
import org.sakaiproject.tool.assessment.qti.util.XmlUtil;
import org.sakaiproject.tool.assessment.samlite.api.QuestionGroup;
import org.sakaiproject.tool.assessment.services.assessment.AssessmentService;
import org.sakaiproject.tool.assessment.services.QuestionPoolService;
import org.sakaiproject.tool.assessment.facade.QuestionPoolIteratorFacade;
import org.sakaiproject.tool.assessment.facade.QuestionPoolFacade;
import org.sakaiproject.tool.assessment.services.qti.QTIService;
import org.sakaiproject.util.FormattedText;
import org.w3c.dom.Document;
Expand All @@ -48,6 +51,7 @@
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
Expand Down Expand Up @@ -329,5 +333,62 @@ private String findAssessmentTemplateId(String title) {
}
return null;
}


/**
* poolAttachmentReport - Makes a report of all attachments included in one specified question pool. Optionally, fix the broken attachments making a copy of them in a new context.
*
* @param String sessionId the id of a valid session for user owner of the pool (NOT admin user)
* @param String user the user EID we want to look into their question pools
* @param Long poolId poolId for searching only in a single pool. Null searches in all pools of the userId.
* @param String contextToReplace a site ID where user has access to, so broken attachments will be copied there and replaced in pool
* @return String a report of the attachments at every pool of the current user and the actions done on them
*
*/
@WebMethod
@Path("/poolAttachmentReport")
@Produces("text/plain")
@GET
public String poolAttachmentReport(
@WebParam(name = "sessionId", partName = "sessionId") @QueryParam("sessionId") String sessionId,
@WebParam(name = "user", partName = "user") @QueryParam("user") String user,
@WebParam(name = "poolId", partName = "poolId") @QueryParam("poolId") String poolId,
@WebParam(name = "contextToReplace", partName = "contextToReplace") @QueryParam("contextToReplace") String contextToReplace)
{
ArrayList<Long> poolIds = new ArrayList<Long>();
StringBuilder resultado = new StringBuilder();

LOG.debug("WS TestsAndQuizzes.poolAttachmentReport(): user - " + user);

String userId=null;
try
{
userId=userDirectoryService.getUserId(user);
}
catch (Exception e)
{
LOG.warn("WS getUserId() failed for user: " + user);
return "";
}

if (contextToReplace.isEmpty()) contextToReplace=null;

Session session = establishSession(sessionId);

if (!poolId.isEmpty()) poolIds.add(new Long(poolId));
else
{
ArrayList qpif = (ArrayList) questionPoolServiceImpl.getAllPools(userId);
for (int i=0;i<qpif.size();i++)
{
QuestionPoolFacade qp = (QuestionPoolFacade) qpif.get(i);
poolIds.add(qp.getQuestionPoolId());
}
}

//Calling the new report function in QuestionPoolService.
for (Long pId: poolIds)
resultado.append(questionPoolServiceImpl.getUserPoolAttachmentReport(userId, pId, contextToReplace));

return resultado.toString();
}
}
Loading

0 comments on commit 0434f4d

Please sign in to comment.