/********************************************************************************** * $URL: $ * $Id: $ *********************************************************************************** * * Copyright (c) 2006, 2007, 2008 The 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.poll.service.impl; import java.util.ArrayList; import java.util.Collection; import java.util.Date; import java.util.HashSet; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Set; import java.util.Stack; import java.util.UUID; import java.util.Vector; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.sakaiproject.entity.api.Entity; import org.sakaiproject.entity.api.EntityManager; import org.sakaiproject.entity.api.EntityTransferrer; import org.sakaiproject.entity.api.HttpAccess; import org.sakaiproject.entity.api.Reference; import org.sakaiproject.entity.api.ResourceProperties; import org.sakaiproject.genericdao.api.search.Order; import org.sakaiproject.genericdao.api.search.Restriction; import org.sakaiproject.genericdao.api.search.Search; import org.sakaiproject.id.api.IdManager; import org.sakaiproject.poll.dao.PollDao; import org.sakaiproject.poll.logic.ExternalLogic; import org.sakaiproject.poll.logic.PollListManager; import org.sakaiproject.poll.logic.PollVoteManager; import org.sakaiproject.poll.model.Option; import org.sakaiproject.poll.model.Poll; import org.sakaiproject.poll.model.Vote; import org.sakaiproject.poll.util.PollUtil; import org.sakaiproject.site.api.SiteService; import org.springframework.dao.DataAccessException; import org.w3c.dom.Document; import org.w3c.dom.Element; import org.w3c.dom.NodeList; public class PollListManagerImpl implements PollListManager,EntityTransferrer { // use commons logger private static Log log = LogFactory.getLog(PollListManagerImpl.class); public static final String REFERENCE_ROOT = Entity.SEPARATOR + "poll"; private EntityManager entityManager; public void setEntityManager(EntityManager em) { entityManager = em; } private IdManager idManager; public void setIdManager(IdManager idm) { idManager = idm; } private PollDao dao; public void setDao(PollDao dao) { this.dao = dao; } private PollVoteManager pollVoteManager; public void setPollVoteManager(PollVoteManager pollVoteManager) { this.pollVoteManager = pollVoteManager; } private ExternalLogic externalLogic; public void setExternalLogic(ExternalLogic externalLogic) { this.externalLogic = externalLogic; } public void init() { try { entityManager.registerEntityProducer(this, REFERENCE_ROOT); } catch (Exception t) { log.warn("init(): ", t); } externalLogic.registerFunction(PERMISSION_VOTE); externalLogic.registerFunction(PERMISSION_ADD); externalLogic.registerFunction(PERMISSION_DELETE_OWN); externalLogic.registerFunction(PERMISSION_DELETE_ANY); externalLogic.registerFunction(PERMISSION_EDIT_ANY); externalLogic.registerFunction(PERMISSION_EDIT_OWN); log.info(this + " init()"); } public void destroy() { } public List<Poll> findAllPollsForUserAndSitesAndPermission(String userId, String[] siteIds, String permissionConstant) { if (userId == null || permissionConstant == null) { throw new IllegalArgumentException("userId and permissionConstant must be set"); } List<Poll> polls = null; // get all allowed sites for this user List<String> allowedSites = externalLogic.getSitesForUser(userId, permissionConstant); if (! allowedSites.isEmpty()) { if (siteIds != null) { if (siteIds.length > 0) { // filter down to just the requested ones for (int j = 0; j < allowedSites.size(); j++) { String siteId = allowedSites.get(j); boolean found = false; for (int i = 0; i < siteIds.length; i++) { if (siteId.equals(siteIds[i])) { found = true; } } if (!found) { allowedSites.remove(j); } } } else { // no sites to search so EXIT here return new ArrayList<Poll>(); } } String[] siteIdsToSearch = allowedSites.toArray(new String[allowedSites.size()]); Search search = new Search(); if (siteIdsToSearch.length > 0) { search.addRestriction(new Restriction("siteId", siteIdsToSearch)); } if (PollListManager.PERMISSION_VOTE.equals(permissionConstant)) { // limit to polls which are open Date now = new Date(); search.addRestriction(new Restriction("voteOpen", now, Restriction.LESS)); search.addRestriction(new Restriction("voteClose", now, Restriction.GREATER)); } else { // show all polls } search.addOrder(new Order("creationDate")); polls = dao.findBySearch(Poll.class, search); } if (polls == null) { polls = new ArrayList<Poll>(); } return polls; } public boolean savePoll(Poll t) throws SecurityException, IllegalArgumentException { boolean newPoll = false; if (t == null || t.getText() == null || t.getSiteId() == null || t.getVoteOpen() == null|| t.getVoteClose() == null) { throw new IllegalArgumentException("you must supply a question, siteId & open and close dates"); } if (!externalLogic.isUserAdmin() && !externalLogic.isAllowedInLocation(PollListManager.PERMISSION_ADD, externalLogic.getSiteRefFromId(t.getSiteId()), externalLogic.getCurrentuserReference())) { throw new SecurityException(); } if (t.getId() == null) { newPoll = true; t.setId(idManager.createUuid()); } try { dao.save(t); } catch (DataAccessException e) { log.error("Hibernate could not save: " + e.toString()); e.printStackTrace(); return false; } log.debug(" Poll " + t.toString() + "successfuly saved"); externalLogic.registerStatement(t.getText(), newPoll); if (newPoll) externalLogic.postEvent("poll.add", "poll/site/" + t.getSiteId() + "/poll/" + t.getId(), true); else externalLogic.postEvent("poll.update", "poll/site/" + t.getSiteId() + " /poll/" + t.getId(), true); return true; } public boolean deletePoll(Poll t) throws SecurityException, IllegalArgumentException { if (t == null) { throw new IllegalArgumentException("Poll can't be null"); } if (t.getPollId() == null) { throw new IllegalArgumentException("Poll id can't be null"); } if (!pollCanDelete(t)) { throw new SecurityException("user:" + externalLogic.getCurrentuserReference() + " can't delete poll: " + t.getId()); } //Delete the Votes List<Vote> vote = t.getVotes(); //We could have a partially populate item if (vote == null || vote.isEmpty()) { log.debug("getting votes as they where null"); vote = pollVoteManager.getAllVotesForPoll(t); log.debug("got " + vote.size() + " vote"); } Set<Vote> voteSet = new HashSet<Vote>(vote); dao.deleteSet(voteSet); //Delete the Options List<Option> options = t.getPollOptions(); //as above we could have a partialy populate item if (options == null || options.isEmpty()) { options = getOptionsForPoll(t); } Set<Option> optionSet = new HashSet<Option>(options); dao.deleteSet(optionSet); dao.delete(t); log.info("Poll id " + t.getId() + " deleted"); externalLogic.postEvent("poll.delete", "poll/site/" + t.getSiteId() + "/poll/" + t.getId(), true); return true; } public List<Poll> findAllPolls(String siteId) { Search search = new Search(); search.addOrder(new Order("creationDate", false)); search.addRestriction(new Restriction("siteId", siteId)); List<Poll> polls = dao.findBySearch(Poll.class, search); return polls; } public Poll getPollById(Long pollId) throws SecurityException { return getPollById(pollId, true); } public Poll getPollById(Long pollId, boolean includeOptions) throws SecurityException { Search search = new Search(); search.addRestriction(new Restriction("pollId", pollId)); Poll poll = dao.findOneBySearch(Poll.class, search); if (poll != null) { if (includeOptions) { List<Option> optionList = getOptionsForPoll(poll); poll.setOptions(optionList); } } if (poll == null) return null; //user needs at least site visit to read a poll if (!externalLogic.isAllowedInLocation("site.visit", externalLogic.getSiteRefFromId(poll.getSiteId()), externalLogic.getCurrentuserReference()) && !externalLogic.isUserAdmin()) { throw new SecurityException("user:" + externalLogic.getCurrentuserReference() + " can't read poll " + pollId); } return poll; } // OPTIONS public List<Option> getOptionsForPoll(Poll poll) { return getOptionsForPoll(poll.getPollId()); } public List<Option> getOptionsForPoll(Long pollId) { Poll poll; try { poll = getPollById(pollId, false); } catch (SecurityException e) { throw new SecurityException(e); } if (poll == null) { throw new IllegalArgumentException("Cannot get options for a poll ("+pollId+") that does not exist"); } Search search = new Search(); search.addRestriction(new Restriction("pollId", pollId)); search.addOrder(new Order("optionId")); List<Option> optionList = dao.findBySearch(Option.class, search); return optionList; } public List<Option> getVisibleOptionsForPoll(Long pollId) { List<Option> options = getOptionsForPoll(pollId); //iterate and remove deleted options for (Iterator<Option> i = options.listIterator(); i.hasNext();) { Option o = i.next(); if (o == null || o.getDeleted()) { i.remove(); } } return options; } public Poll getPollWithVotes(Long pollId) { Search search = new Search(); search.addRestriction(new Restriction("pollId", pollId)); return dao.findOneBySearch(Poll.class, search); } public Option getOptionById(Long optionId) { Search search = new Search(); search.addRestriction(new Restriction("optionId", optionId)); Option option = dao.findOneBySearch(Option.class, search); // if the id is null set it if (option != null && option.getUUId() == null) { option.setUUId( UUID.randomUUID().toString() ); saveOption(option); } return option; } public void deleteOption(Option option) { try { dao.delete(option); } catch (DataAccessException e) { log.error("Hibernate could not delete: " + e.toString()); e.printStackTrace(); return; } log.info("Option id " + option.getId() + " deleted"); } public void deleteOption(Option option, boolean soft) { if (!soft) { deleteOption(option); return; } else { try { option.setDeleted(Boolean.TRUE); dao.save(option); log.info("Option id " + option.getId() + " soft deleted."); } catch (DataAccessException e) { log.error("Hibernate could not soft delete delete!", e); return; } } } public boolean saveOption(Option t) { if (t.getUUId() == null || t.getUUId().trim().length() == 0) { t.setUUId( UUID.randomUUID().toString() ); } try { dao.save(t); } catch (DataAccessException e) { log.error("Hibernate could not save: " + e.toString()); e.printStackTrace(); return false; } log.info("Option " + t.toString() + "successfuly saved"); return true; } // INTERNAL private boolean pollCanDelete(Poll poll) { if (externalLogic.isUserAdmin() || this.isSiteOwner(poll.getSiteId())) return true; if (externalLogic.isAllowedInLocation(PERMISSION_DELETE_ANY, externalLogic.getSiteRefFromId(poll.getSiteId()))) return true; if (externalLogic.isAllowedInLocation(PERMISSION_DELETE_OWN, externalLogic.getSiteRefFromId(poll.getSiteId())) && poll.getOwner().equals(externalLogic.getCurrentUserId())) return true; return false; } private boolean isSiteOwner(String siteId) { if (externalLogic.isUserAdmin()) return true; else if (externalLogic.isAllowedInLocation("site.upd", externalLogic.getSiteRefFromId(siteId))) return true; else return false; } /* * EntityProducer Methods */ public String getLabel() { return "poll"; } public boolean willArchiveMerge() { return true; } @SuppressWarnings("unchecked") public String archive(String siteId, Document doc, Stack stack, String archivePath, List attachments) { log.debug("archive: poll " + siteId); // prepare the buffer for the results log StringBuilder results = new StringBuilder(); // String assignRef = assignmentReference(siteId, SiteService.MAIN_CONTAINER); results.append("archiving " + getLabel() + " context " + Entity.SEPARATOR + siteId + Entity.SEPARATOR + SiteService.MAIN_CONTAINER + ".\n"); // start with an element with our very own (service) name Element element = doc.createElement(PollListManager.class.getName()); ((Element) stack.peek()).appendChild(element); stack.push(element); List pollsList = findAllPolls(siteId); log.debug("got list of " + pollsList.size() + " polls"); for (int i = 0; pollsList.size() > i; i++) { try { Poll poll = (Poll) pollsList.get(i); log.info("got poll " + poll.getId()); // archive this assignment Element el = poll.toXml(doc, stack); // since we aren't archiving votes too, don't worry about archiving the // soft-deleted options -- only "visible". List options = getVisibleOptionsForPoll(poll.getPollId()); for (int q = 0; options.size() > q; q++) { Option opt = (Option) options.get(q); Element el2 = PollUtil.optionToXml(opt, doc, stack); el.appendChild(el2); } element.appendChild(el); } catch (Exception e) { e.printStackTrace(); } } // while stack.pop(); return results.toString(); } public String merge(String siteId, Element root, String archivePath, String fromSiteId, Map<String, String> attachmentNames, Map<String, String> userIdTrans, Set<String> userListAllowImport) { /* USERS ARE NOT MERGED */ NodeList polls = root.getElementsByTagName("poll"); for (int i=0; i<polls.getLength(); ++i) { Element pollElement = (Element) polls.item(i); Poll poll = Poll.fromXML(pollElement); poll.setSiteId(siteId); savePoll(poll); NodeList options = pollElement.getElementsByTagName("option"); for (int j=0; j<options.getLength(); ++j) { Element optionElement = (Element) options.item(j); Option option = PollUtil.xmlToOption(optionElement); option.setPollId(poll.getPollId()); saveOption(option); } } return null; } public boolean parseEntityReference(String reference, Reference ref) { if (reference.startsWith(REFERENCE_ROOT)) { // /syllabus/siteid/syllabusid String[] parts = split(reference, Entity.SEPARATOR); String subType = ""; String context = null; String id = null; String container = ""; if (parts.length > 2) { // the site/context context = parts[2]; // the id if (parts.length > 3) { id = parts[3]; } } ref.set(PollListManager.class.getName(), subType, id, container, context); return true; } return false; } public String getEntityDescription(Reference arg0) { // TODO Auto-generated method stub return null; } public ResourceProperties getEntityResourceProperties(Reference arg0) { // TODO Auto-generated method stub return null; } public Entity getEntity(Reference ref) { // TODO Auto-generated method stub Entity rv = null; if (REF_POLL_TYPE.equals(ref.getSubType())) { rv = getPoll(ref.getReference()); } return rv; } public String getEntityUrl(Reference arg0) { // TODO Auto-generated method stub return null; } public Collection getEntityAuthzGroups(Reference arg0, String arg1) { // TODO Auto-generated method stub return null; } public HttpAccess getHttpAccess() { // TODO Auto-generated method stub return null; } /** * {@inheritDoc} */ public String[] myToolIds() { String[] toolIds = { "sakai.poll"}; return toolIds; } public void transferCopyEntities(String fromContext, String toContext, List resourceIds, boolean condition){ transferCopyEntities(fromContext, toContext, resourceIds); } public void transferCopyEntities(String fromContext, String toContext, List resourceIds){ try{ Iterator<Poll> fromPolls = findAllPolls(fromContext).iterator(); while (fromPolls.hasNext()){ Poll fromPoll = fromPolls.next(); Poll fromPollV = getPollWithVotes(fromPoll.getPollId()); Poll toPoll = (Poll) new Poll(); toPoll.setOwner(fromPollV.getOwner()); toPoll.setSiteId(toContext); toPoll.setCreationDate(fromPollV.getCreationDate()); toPoll.setText(fromPollV.getText()); toPoll.setMinOptions(fromPollV.getMinOptions()); toPoll.setMaxOptions(fromPollV.getMaxOptions()); toPoll.setVoteOpen(fromPollV.getVoteOpen()); toPoll.setVoteClose(fromPollV.getVoteClose()); toPoll.setDisplayResult(fromPollV.getDisplayResult()); toPoll.setLimitVoting(fromPollV.getLimitVoting()); toPoll.setDetails(fromPollV.getDetails()); //Guardamos toPoll para que se puedan ir añandiéndole las opciones y los votos savePoll(toPoll); //Añadimos las opciones List<Option> options = getOptionsForPoll(fromPoll); if (options!=null){ Iterator<Option> fromOptions = options.iterator(); while (fromOptions.hasNext()){ Option fromOption = (Option) fromOptions.next(); Option toOption = (Option) new Option(); toOption.setOptionText(fromOption.getOptionText()); toOption.setStatus(fromOption.getStatus()); toOption.setPollId(toPoll.getPollId()); toOption.setDeleted(fromOption.getDeleted()); saveOption(toOption); toPoll.addOption(toOption); } } //Añadimos los votos List<Vote> votes = fromPollV.getVotes(); if (votes!=null){ Iterator<Vote> fromVotes = votes.iterator(); while (fromVotes.hasNext()){ toPoll.addVote((Vote)fromVotes.next()); } } //Actualizamos toPoll savePoll(toPoll); } }catch(Exception e){ e.printStackTrace(); } } protected String[] split(String source, String splitter) { // hold the results as we find them Vector<String> rv = new Vector<String>(); int last = 0; int next = 0; do { // find next splitter in source next = source.indexOf(splitter, last); if (next != -1) { // isolate from last thru before next rv.add(source.substring(last, next)); last = next + splitter.length(); } } while (next != -1); if (last < source.length()) { rv.add(source.substring(last, source.length())); } // convert to array return (String[]) rv.toArray(new String[rv.size()]); } // split public Poll getPoll(String ref) { // we need to get the options here Search search = new Search(); search.addRestriction(new Restriction("id", ref)); Poll poll = dao.findOneBySearch(Poll.class, search); // if the id is null set it if (poll.getId() == null) { poll.setId(idManager.createUuid()); savePoll(poll); } return poll; } public boolean isAllowedViewResults(Poll poll, String userId) { if (externalLogic.isUserAdmin()) return true; if (poll.getDisplayResult().equals("open")) return true; if (poll.getDisplayResult().equals("afterVoting")) { Search search = new Search(); search.addRestriction(new Restriction("pollId", poll.getPollId())); search.addRestriction(new Restriction("userId", userId)); List<Vote> votes = dao.findBySearch(Vote.class, search); //System.out.println("got " + pollCollection.size() + "votes for this poll"); if (votes.size() > 0) return true; } if ((poll.getDisplayResult().equals("afterClosing") || poll.getDisplayResult().equals("afterVoting") )&& poll.getVoteClose().before(new Date())) return true; //the owner can view the results if(poll.getOwner().equals(userId) && !externalLogic.userIsViewingAsRole()) return true; return false; } public boolean isPollPublic(Poll poll) { //is this poll public? if(poll.getIsPublic()){ return true; } //can the anonymous user vote? if(externalLogic.isAllowedInLocation(PollListManager.PERMISSION_VOTE, externalLogic.getSiteRefFromId(poll.getSiteId()))){ return true; } return false; } }