blob: 058b3033860fa462fd071b0f7a0fdb254eeb4741 [file] [log] [blame]
/*******************************************************************************
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache 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.apache.org/licenses/LICENSE-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.ofbiz.content.survey;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.StringWriter;
import java.io.Writer;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.Iterator;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import javolution.util.FastList;
import javolution.util.FastMap;
import javolution.util.FastSet;
import org.ofbiz.base.location.FlexibleLocation;
import org.ofbiz.base.util.Debug;
import org.ofbiz.base.util.GeneralException;
import org.ofbiz.base.util.UtilMisc;
import org.ofbiz.base.util.UtilValidate;
import org.ofbiz.base.util.template.FreeMarkerWorker;
import org.ofbiz.entity.Delegator;
import org.ofbiz.entity.GenericEntityException;
import org.ofbiz.entity.GenericValue;
import org.ofbiz.entity.condition.EntityCondition;
import org.ofbiz.entity.condition.EntityOperator;
import org.ofbiz.entity.transaction.TransactionUtil;
import org.ofbiz.entity.util.EntityFindOptions;
import org.ofbiz.entity.util.EntityListIterator;
import org.ofbiz.entity.util.EntityUtil;
import freemarker.template.Configuration;
import freemarker.template.Template;
import freemarker.template.TemplateException;
/**
* Survey Wrapper - Class to render survey forms
*/
public class SurveyWrapper {
public static final String module = SurveyWrapper.class.getName();
protected Delegator delegator = null;
protected String responseId = null;
protected String partyId = null;
protected String surveyId = null;
protected Map<String, Object> templateContext = null;
protected Map<String, Object> passThru = null;
protected Map<String, Object> defaultValues = null;
protected boolean edit = false;
protected SurveyWrapper() {}
public SurveyWrapper(Delegator delegator, String responseId, String partyId, String surveyId, Map<String, Object> passThru, Map<String, Object> defaultValues) {
this.delegator = delegator;
this.responseId = responseId;
this.partyId = partyId;
this.surveyId = surveyId;
this.setPassThru(passThru);
this.setDefaultValues(defaultValues);
this.checkParameters();
}
public SurveyWrapper(Delegator delegator, String responseId, String partyId, String surveyId, Map<String, Object> passThru) {
this(delegator, responseId, partyId, surveyId, passThru, null);
}
public SurveyWrapper(Delegator delegator, String surveyId) {
this(delegator, null, null, surveyId, null);
}
protected void checkParameters() {
if (delegator == null || surveyId == null) {
throw new IllegalArgumentException("Missing one or more required parameters (delegator, surveyId)");
}
}
/**
* Sets the pass-thru values (hidden form fields)
* @param passThru
*/
public void setPassThru(Map<String, Object> passThru) {
if (passThru != null) {
this.passThru = FastMap.newInstance();
this.passThru.putAll(passThru);
}
}
/**
* Sets the default values
* @param defaultValues
*/
public void setDefaultValues(Map<String, Object> defaultValues) {
if (defaultValues != null) {
this.defaultValues = FastMap.newInstance();
this.defaultValues.putAll(defaultValues);
}
}
/**
* Adds an object to the FTL survey template context
* @param name
* @param value
*/
public void addToTemplateContext(String name, Object value) {
if (templateContext == null) {
templateContext = FastMap.newInstance();
}
templateContext.put(name, value);
}
/**
* Removes an object from the FTL survey template context
* @param name
*/
public void removeFromTemplateContext(String name) {
if (templateContext != null)
templateContext.remove(name);
}
/**
* Renders the Survey
* @return Writer object from the parsed Freemarker Template
* @throws SurveyWrapperException
*/
public Writer render(String templatePath) throws SurveyWrapperException {
URL templateUrl = null;
try {
templateUrl = FlexibleLocation.resolveLocation(templatePath);
} catch (MalformedURLException e) {
throw new SurveyWrapperException(e);
}
if (templateUrl == null) {
String errMsg = "Problem getting the template for Survey from URL: " + templatePath;
Debug.logError(errMsg, module);
throw new IllegalArgumentException(errMsg);
}
Writer writer = new StringWriter();
this.render(templateUrl, writer);
return writer;
}
/**
* Renders the Survey
* @param templateUrl the template URL
* @param writer the write
* @throws SurveyWrapperException
*/
public void render(URL templateUrl, Writer writer) throws SurveyWrapperException {
String responseId = this.getThisResponseId();
GenericValue survey = this.getSurvey();
List<GenericValue> surveyQuestionAndAppls = this.getSurveyQuestionAndAppls();
Map<String, Object> results = this.getResults(surveyQuestionAndAppls);
Map<String, Object> currentAnswers = null;
if (responseId != null && canUpdate()) {
currentAnswers = this.getResponseAnswers(responseId);
} else {
currentAnswers = this.getResponseAnswers(null);
}
Map<String, Object> sqaaWithColIdListByMultiRespId = FastMap.newInstance();
for(GenericValue surveyQuestionAndAppl : surveyQuestionAndAppls) {
String surveyMultiRespColId = surveyQuestionAndAppl.getString("surveyMultiRespColId");
if (UtilValidate.isNotEmpty(surveyMultiRespColId)) {
String surveyMultiRespId = surveyQuestionAndAppl.getString("surveyMultiRespId");
UtilMisc.addToListInMap(surveyQuestionAndAppl, sqaaWithColIdListByMultiRespId, surveyMultiRespId);
}
}
if (templateContext == null) {
templateContext = FastMap.newInstance();
}
templateContext.put("partyId", partyId);
templateContext.put("survey", survey);
templateContext.put("surveyResults", results);
templateContext.put("surveyQuestionAndAppls", surveyQuestionAndAppls);
templateContext.put("sqaaWithColIdListByMultiRespId", sqaaWithColIdListByMultiRespId);
templateContext.put("alreadyShownSqaaPkWithColId", FastSet.newInstance());
templateContext.put("surveyAnswers", currentAnswers);
templateContext.put("surveyResponseId", responseId);
templateContext.put("sequenceSort", UtilMisc.toList("sequenceNum"));
templateContext.put("additionalFields", passThru);
templateContext.put("defaultValues", defaultValues);
templateContext.put("delegator", this.delegator);
templateContext.put("locale", Locale.getDefault());
Template template = this.getTemplate(templateUrl);
try {
FreeMarkerWorker.renderTemplate(template, templateContext, writer);
} catch (TemplateException e) {
Debug.logError(e, "Error rendering Survey with template at [" + templateUrl.toExternalForm() + "]", module);
} catch (IOException e) {
Debug.logError(e, "Error rendering Survey with template at [" + templateUrl.toExternalForm() + "]", module);
}
}
// returns the FTL Template object
// Note: the template will not be cached
protected Template getTemplate(URL templateUrl) {
Configuration config = FreeMarkerWorker.getDefaultOfbizConfig();
Template template = null;
try {
InputStream templateStream = templateUrl.openStream();
InputStreamReader templateReader = new InputStreamReader(templateStream);
template = new Template(templateUrl.toExternalForm(), templateReader, config);
} catch (IOException e) {
Debug.logError(e, "Unable to get template from URL :" + templateUrl.toExternalForm(), module);
}
return template;
}
public void setEdit(boolean edit) {
this.edit = edit;
}
// returns the GenericValue object for the current Survey
public GenericValue getSurvey() {
GenericValue survey = null;
try {
survey = delegator.findByPrimaryKeyCache("Survey", UtilMisc.toMap("surveyId", surveyId));
} catch (GenericEntityException e) {
Debug.logError(e, "Unable to get Survey : " + surveyId, module);
}
return survey;
}
public String getSurveyName() {
GenericValue survey = this.getSurvey();
if (survey != null) {
return survey.getString("surveyName");
}
return "";
}
// true if we can update this survey
public boolean canUpdate() {
if (this.edit) {
return true;
}
GenericValue survey = this.getSurvey();
if (!"Y".equals(survey.getString("allowMultiple")) && !"Y".equals(survey.getString("allowUpdate"))) {
return false;
}
return true;
}
public boolean canRespond() {
String responseId = this.getThisResponseId();
if (responseId == null) {
return true;
} else {
GenericValue survey = this.getSurvey();
if ("Y".equals(survey.getString("allowMultiple"))) {
return true;
}
}
return false;
}
// returns a list of SurveyQuestions (in order by sequence number) for the current Survey
public List<GenericValue> getSurveyQuestionAndAppls() {
List<GenericValue> questions = FastList.newInstance();
try {
Map<String, Object> fields = UtilMisc.<String, Object>toMap("surveyId", surveyId);
List<String> order = UtilMisc.<String>toList("sequenceNum", "surveyMultiRespColId");
questions = delegator.findByAndCache("SurveyQuestionAndAppl", fields, order);
if (questions != null) {
questions = EntityUtil.filterByDate(questions);
}
} catch (GenericEntityException e) {
Debug.logError(e, "Unable to get questions for survey : " + surveyId, module);
}
return questions;
}
// returns the most current SurveyResponse ID for a survey; null if no party is found
protected String getThisResponseId() {
if (responseId != null) {
return responseId;
}
if (partyId == null) {
return null;
}
String responseId = null;
List<GenericValue> responses = null;
try {
responses = delegator.findByAnd("SurveyResponse", UtilMisc.toMap("surveyId", surveyId, "partyId", partyId), UtilMisc.toList("-lastModifiedDate"));
} catch (GenericEntityException e) {
Debug.logError(e, module);
}
if (UtilValidate.isNotEmpty(responses)) {
GenericValue response = EntityUtil.getFirst(responses);
responseId = response.getString("surveyResponseId");
if (responses.size() > 1) {
Debug.logWarning("More then one response found for survey : " + surveyId + " by party : " + partyId + " using most current", module);
}
}
return responseId;
}
protected void setThisResponseId(String responseId) {
this.responseId = responseId;
}
public long getNumberResponses() throws SurveyWrapperException {
long responses = 0;
try {
responses = delegator.findCountByCondition("SurveyResponse", EntityCondition.makeCondition("surveyId", EntityOperator.EQUALS, surveyId), null, null);
} catch (GenericEntityException e) {
throw new SurveyWrapperException(e);
}
return responses;
}
public List<GenericValue> getSurveyResponses(GenericValue question) throws SurveyWrapperException {
List<GenericValue> responses = null;
try {
responses = delegator.findByAnd("SurveyResponse", UtilMisc.toMap("surveyQuestionId", question.getString("surveyQuestionId")));
} catch (GenericEntityException e) {
throw new SurveyWrapperException(e);
}
return responses;
}
// returns a Map of answers keyed on SurveyQuestion ID from the most current SurveyResponse ID
public Map<String, Object> getResponseAnswers(String responseId) throws SurveyWrapperException {
Map<String, Object> answerMap = FastMap.newInstance();
if (responseId != null) {
List<GenericValue> answers = null;
try {
answers = delegator.findByAnd("SurveyResponseAnswer", UtilMisc.toMap("surveyResponseId", responseId));
} catch (GenericEntityException e) {
Debug.logError(e, module);
}
if (UtilValidate.isNotEmpty(answers)) {
for(GenericValue answer : answers) {
answerMap.put(answer.getString("surveyQuestionId"), answer);
}
}
}
// get the pass-thru (posted form data)
if (UtilValidate.isNotEmpty(passThru)) {
for(String key : passThru.keySet()) {
if (key.toUpperCase().startsWith("ANSWERS_")) {
int splitIndex = key.indexOf('_');
String questionId = key.substring(splitIndex+1);
Map<String, Object> thisAnswer = FastMap.newInstance();
String answer = (String) passThru.remove(key);
thisAnswer.put("booleanResponse", answer);
thisAnswer.put("currencyResponse", answer);
thisAnswer.put("floatResponse", answer);
thisAnswer.put("numericResponse", answer);
thisAnswer.put("textResponse", answer);
thisAnswer.put("surveyOptionSeqId", answer);
// this is okay since only one will be looked at
answerMap.put(questionId, thisAnswer);
}
}
}
return answerMap;
}
public List<GenericValue> getQuestionResponses(GenericValue question, int startIndex, int number) throws SurveyWrapperException {
List<GenericValue> resp = null;
boolean beganTransaction = false;
try {
beganTransaction = TransactionUtil.begin();
int maxRows = startIndex + number;
EntityListIterator eli = this.getEli(question, maxRows);
if (startIndex > 0 && number > 0) {
resp = eli.getPartialList(startIndex, number);
} else {
resp = eli.getCompleteList();
}
eli.close();
} catch (GenericEntityException e) {
try {
// only rollback the transaction if we started one...
TransactionUtil.rollback(beganTransaction, "Error getting survey question responses", e);
} catch (GenericEntityException e2) {
Debug.logError(e2, "Could not rollback transaction: " + e2.toString(), module);
}
throw new SurveyWrapperException(e);
} finally {
try {
// only commit the transaction if we started one...
TransactionUtil.commit(beganTransaction);
} catch (GenericEntityException e) {
throw new SurveyWrapperException(e);
//Debug.logError(e, "Could not commit transaction: " + e.toString(), module);
}
}
return resp;
}
public Map<String, Object> getResults(List<GenericValue> questions) throws SurveyWrapperException {
Map<String, Object> questionResults = FastMap.newInstance();
if (questions != null) {
for(GenericValue question : questions) {
Map<String, Object> results = getResultInfo(question);
if (results != null) {
questionResults.put(question.getString("surveyQuestionId"), results);
}
}
}
return questionResults;
}
// returns a map of question reqsults
public Map<String, Object> getResultInfo(GenericValue question) throws SurveyWrapperException {
Map<String, Object> resultMap = FastMap.newInstance();
// special keys in the result:
// "_q_type" - question type (SurveyQuestionTypeId)
// "_a_type" - answer type ("boolean", "option", "long", "double", "text")
// "_total" - number of total responses (all types)
// "_tally" - tally of all response values (number types)
// "_average" - average of all response values (number types)
// "_yes_total" - number of 'Y' (true) reponses (boolean type)
// "_no_total" - number of 'N' (false) responses (boolean type)
// "_yes_percent" - number of 'Y' (true) reponses (boolean type)
// "_no_percent" - number of 'N' (false) responses (boolean type)
// [optionId] - Map containing '_total, _percent' keys (option type)
String questionType = question.getString("surveyQuestionTypeId");
resultMap.put("_q_type", questionType);
// call the proper method based on the question type
// note this will need to be updated as new types are added
if ("OPTION".equals(questionType)) {
Map<String, Object> thisResult = getOptionResult(question);
if (thisResult != null) {
Long questionTotal = (Long) thisResult.remove("_total");
if (questionTotal == null) {
questionTotal = Long.valueOf(0);
}
// set the total responses
resultMap.put("_total", questionTotal);
// create the map of option info ("_total", "_percent")
for(String optId : thisResult.keySet()) {
Map<String, Object> optMap = FastMap.newInstance();
Long optTotal = (Long) thisResult.get(optId);
if (optTotal == null) {
optTotal = Long.valueOf(0);
}
Long percent = Long.valueOf((long)(((double)optTotal.longValue() / (double)questionTotal.longValue()) * 100));
optMap.put("_total", optTotal);
optMap.put("_percent", percent);
resultMap.put(optId, optMap);
}
resultMap.put("_a_type", "option");
}
} else if ("BOOLEAN".equals(questionType)) {
long[] thisResult = getBooleanResult(question);
long yesPercent = thisResult[1] > 0 ? (long)(((double)thisResult[1] / (double)thisResult[0]) * 100) : 0;
long noPercent = thisResult[2] > 0 ? (long)(((double)thisResult[2] / (double)thisResult[0]) * 100) : 0;
resultMap.put("_total", Long.valueOf(thisResult[0]));
resultMap.put("_yes_total", Long.valueOf(thisResult[1]));
resultMap.put("_no_total", Long.valueOf(thisResult[2]));
resultMap.put("_yes_percent", Long.valueOf(yesPercent));
resultMap.put("_no_percent", Long.valueOf(noPercent));
resultMap.put("_a_type", "boolean");
} else if ("NUMBER_LONG".equals(questionType)) {
double[] thisResult = getNumberResult(question, 1);
resultMap.put("_total", Long.valueOf((long)thisResult[0]));
resultMap.put("_tally", Long.valueOf((long)thisResult[1]));
resultMap.put("_average", Long.valueOf((long)thisResult[2]));
resultMap.put("_a_type", "long");
} else if ("NUMBER_CURRENCY".equals(questionType)) {
double[] thisResult = getNumberResult(question, 2);
resultMap.put("_total", Long.valueOf((long)thisResult[0]));
resultMap.put("_tally", Double.valueOf(thisResult[1]));
resultMap.put("_average", Double.valueOf(thisResult[2]));
resultMap.put("_a_type", "double");
} else if ("NUMBER_FLOAT".equals(questionType)) {
double[] thisResult = getNumberResult(question, 3);
resultMap.put("_total", Long.valueOf((long)thisResult[0]));
resultMap.put("_tally", Double.valueOf(thisResult[1]));
resultMap.put("_average", Double.valueOf(thisResult[2]));
resultMap.put("_a_type", "double");
} else if ("SEPERATOR_LINE".equals(questionType) || "SEPERATOR_TEXT".equals(questionType)) {
// not really a question; ingore completely
return null;
} else {
// default is text
resultMap.put("_total", Long.valueOf(getTextResult(question)));
resultMap.put("_a_type", "text");
}
return resultMap;
}
private long[] getBooleanResult(GenericValue question) throws SurveyWrapperException {
boolean beganTransaction = false;
try {
beganTransaction = TransactionUtil.begin();
long[] result = { 0, 0, 0 };
// index 0 = total responses
// index 1 = total yes
// index 2 = total no
EntityListIterator eli = this.getEli(question, -1);
if (eli != null) {
GenericValue value;
while (((value = eli.next()) != null)) {
if ("Y".equalsIgnoreCase(value.getString("booleanResponse"))) {
result[1]++;
} else {
result[2]++;
}
result[0]++; // increment the count
}
eli.close();
}
return result;
} catch (GenericEntityException e) {
try {
// only rollback the transaction if we started one...
TransactionUtil.rollback(beganTransaction, "Error getting survey question responses Boolean result", e);
} catch (GenericEntityException e2) {
Debug.logError(e2, "Could not rollback transaction: " + e2.toString(), module);
}
throw new SurveyWrapperException(e);
} finally {
try {
// only commit the transaction if we started one...
TransactionUtil.commit(beganTransaction);
} catch (GenericEntityException e) {
throw new SurveyWrapperException(e);
//Debug.logError(e, "Could not commit transaction: " + e.toString(), module);
}
}
}
private double[] getNumberResult(GenericValue question, int type) throws SurveyWrapperException {
double[] result = { 0, 0, 0 };
// index 0 = total responses
// index 1 = tally
// index 2 = average
boolean beganTransaction = false;
try {
beganTransaction = TransactionUtil.begin();
EntityListIterator eli = this.getEli(question, -1);
if (eli != null) {
GenericValue value;
while (((value = eli.next()) != null)) {
switch (type) {
case 1:
Long n = value.getLong("numericResponse");
if (UtilValidate.isNotEmpty(n)) {
result[1] += n.longValue();
}
break;
case 2:
Double c = value.getDouble("currencyResponse");
if (UtilValidate.isNotEmpty(c)) {
result[1] += (((double) Math.round((c.doubleValue() - c.doubleValue()) * 100)) / 100);
}
break;
case 3:
Double f = value.getDouble("floatResponse");
if (UtilValidate.isNotEmpty(f)) {
result[1] += f.doubleValue();
}
break;
}
result[0]++; // increment the count
}
eli.close();
}
} catch (GenericEntityException e) {
try {
// only rollback the transaction if we started one...
TransactionUtil.rollback(beganTransaction, "Error getting survey question responses Number result", e);
} catch (GenericEntityException e2) {
Debug.logError(e2, "Could not rollback transaction: " + e2.toString(), module);
}
throw new SurveyWrapperException(e);
} finally {
try {
// only commit the transaction if we started one...
TransactionUtil.commit(beganTransaction);
} catch (GenericEntityException e) {
throw new SurveyWrapperException(e);
//Debug.logError(e, "Could not commit transaction: " + e.toString(), module);
}
}
// average
switch (type) {
case 1:
if (result[0] > 0)
result[2] = ((long) result[1]) / ((long) result[0]);
break;
case 2:
if (result[0] > 0)
result[2] = (((double) Math.round((result[1] / result[0]) * 100)) / 100);
break;
case 3:
if (result[0] > 0)
result[2] = result[1] / result[0];
break;
}
return result;
}
private long getTextResult(GenericValue question) throws SurveyWrapperException {
long result = 0;
try {
result = delegator.findCountByCondition("SurveyResponseAndAnswer", makeEliCondition(question), null, null);
} catch (GenericEntityException e) {
Debug.logError(e, module);
throw new SurveyWrapperException("Unable to get responses", e);
}
return result;
}
private Map<String, Object> getOptionResult(GenericValue question) throws SurveyWrapperException {
Map<String, Object> result = FastMap.newInstance();
long total = 0;
boolean beganTransaction = false;
try {
beganTransaction = TransactionUtil.begin();
EntityListIterator eli = this.getEli(question, -1);
if (eli != null) {
GenericValue value;
while (((value = eli.next()) != null)) {
String optionId = value.getString("surveyOptionSeqId");
if (UtilValidate.isNotEmpty(optionId)) {
Long optCount = (Long) result.remove(optionId);
if (optCount == null) {
optCount = Long.valueOf(1);
} else {
optCount = Long.valueOf(1 + optCount.longValue());
}
result.put(optionId, optCount);
total++; // increment the count
}
}
eli.close();
}
} catch (GenericEntityException e) {
try {
// only rollback the transaction if we started one...
TransactionUtil.rollback(beganTransaction, "Error getting survey question responses Option result", e);
} catch (GenericEntityException e2) {
Debug.logError(e2, "Could not rollback transaction: " + e2.toString(), module);
}
throw new SurveyWrapperException(e);
} finally {
try {
// only commit the transaction if we started one...
TransactionUtil.commit(beganTransaction);
} catch (GenericEntityException e) {
throw new SurveyWrapperException(e);
//Debug.logError(e, "Could not commit transaction: " + e.toString(), module);
}
}
result.put("_total", Long.valueOf(total));
return result;
}
private EntityCondition makeEliCondition(GenericValue question) {
return EntityCondition.makeCondition(UtilMisc.toList(EntityCondition.makeCondition("surveyQuestionId",
EntityOperator.EQUALS, question.getString("surveyQuestionId")),
EntityCondition.makeCondition("surveyId", EntityOperator.EQUALS, surveyId)), EntityOperator.AND);
}
private EntityListIterator getEli(GenericValue question, int maxRows) throws GenericEntityException {
EntityFindOptions efo = new EntityFindOptions();
efo.setResultSetType(EntityFindOptions.TYPE_SCROLL_INSENSITIVE);
efo.setResultSetConcurrency(EntityFindOptions.CONCUR_READ_ONLY);
efo.setSpecifyTypeAndConcur(true);
efo.setDistinct(false);
if (maxRows > 0) {
efo.setMaxRows(maxRows);
}
EntityListIterator eli = null;
eli = delegator.find("SurveyResponseAndAnswer", makeEliCondition(question), null, null, null, efo);
return eli;
}
@SuppressWarnings("serial")
protected class SurveyWrapperException extends GeneralException {
public SurveyWrapperException() {
super();
}
public SurveyWrapperException(String str) {
super(str);
}
public SurveyWrapperException(String str, Throwable nested) {
super(str, nested);
}
public SurveyWrapperException(Throwable nested) {
super(nested);
}
}
}