/**
*
*/
package net.frontlinesms.plugins.forms;
import java.util.Collection;
import java.util.HashSet;
import java.util.List;
import net.frontlinesms.FrontlineSMS;
import net.frontlinesms.data.domain.Contact;
import net.frontlinesms.data.domain.FrontlineMessage;
import net.frontlinesms.data.repository.ContactDao;
import net.frontlinesms.listener.IncomingMessageListener;
import net.frontlinesms.plugins.BasePluginController;
import net.frontlinesms.plugins.PluginController;
import net.frontlinesms.plugins.PluginControllerProperties;
import net.frontlinesms.plugins.PluginInitialisationException;
import net.frontlinesms.plugins.forms.data.FormHandlingException;
import net.frontlinesms.plugins.forms.data.domain.Form;
import net.frontlinesms.plugins.forms.data.domain.FormResponse;
import net.frontlinesms.plugins.forms.data.domain.ResponseValue;
import net.frontlinesms.plugins.forms.data.repository.FormDao;
import net.frontlinesms.plugins.forms.data.repository.FormResponseDao;
import net.frontlinesms.plugins.forms.request.DataSubmissionRequest;
import net.frontlinesms.plugins.forms.request.FormsRequestDescription;
import net.frontlinesms.plugins.forms.request.NewFormRequest;
import net.frontlinesms.plugins.forms.request.SubmittedFormData;
import net.frontlinesms.plugins.forms.response.FormsResponseDescription;
import net.frontlinesms.plugins.forms.response.NewFormsResponse;
import net.frontlinesms.plugins.forms.response.SubmittedDataResponse;
import net.frontlinesms.plugins.forms.ui.FormsThinletTabController;
import net.frontlinesms.ui.UiGeneratorController;
import net.frontlinesms.ui.i18n.InternationalisationUtils;
import net.frontlinesms.ui.i18n.TextResourceKeyOwner;
import org.springframework.beans.factory.annotation.Required;
import org.springframework.context.ApplicationContext;
/**
* Controller for the FrontlineForms plugin.
* @author Alex
*/
@PluginControllerProperties(name="Forms", i18nKey="plugins.forms.forms", iconPath="/icons/form.png",
springConfigLocation="classpath:net/frontlinesms/plugins/forms/frontlineforms-spring-hibernate.xml",
hibernateConfigPath="classpath:net/frontlinesms/plugins/forms/frontlineforms.hibernate.cfg.xml")
@TextResourceKeyOwner
public class FormsPluginController extends BasePluginController implements IncomingMessageListener {
//> CONSTANTS
/** Filename and path of the XML for the FrontlineForms tab. */
private static final String XML_FORMS_TAB = "/ui/plugins/forms/formsTab.xml";
/** I18n Text key: SMS text: "There is a new form available: xyz" */
private static final String I18N_NEW_FORMS_SMS = "sms.form.available";
//> INSTANCE PROPERTIES
/** the {@link FrontlineSMS} instance that this plugin is attached to */
private FrontlineSMS frontlineController;
/** the {@link FormsMessageHandler} for processing incoming and outgoing messages */
private FormsMessageHandler formsMessageHandler;
/** DAO for forms */
private FormDao formDao;
/** DAO for contacts */
private ContactDao contactDao;
/** DAO for form responses */
private FormResponseDao formResponseDao;
//> CONFIG METHODS
/** @see PluginController#init(FrontlineSMS, ApplicationContext) */
public void init(FrontlineSMS frontlineController, ApplicationContext applicationContext) throws PluginInitialisationException {
this.frontlineController = frontlineController;
this.contactDao = frontlineController.getContactDao();
this.frontlineController.addIncomingMessageListener(this);
try {
this.formDao = (FormDao) applicationContext.getBean("formDao");
this.formResponseDao = (FormResponseDao) applicationContext.getBean("formResponseDao");
String handlerClassName = FormsProperties.getInstance().getHandlerClassName();
setHandler(handlerClassName);
} catch(Throwable t) {
log.warn("Unable to load form handler class.", t);
throw new PluginInitialisationException(t);
}
}
void setHandler(String handlerClassName) throws InstantiationException, IllegalAccessException, ClassNotFoundException {
this.formsMessageHandler = (FormsMessageHandler) Class.forName(handlerClassName).newInstance();
this.formsMessageHandler.init(this);
}
//> ACCESSORS
/** @return {@link #formsMessageHandler} */
public FormsMessageHandler getHandler() {
return this.formsMessageHandler;
}
/** @return {@link #frontlineController} */
public FrontlineSMS getFrontlineController() {
return this.frontlineController;
}
/** @param frontlineController new value for {@link #frontlineController} */
void setFrontlineController(FrontlineSMS frontlineController) {
this.frontlineController = frontlineController;
}
void setFormsMessageHandler(FormsMessageHandler formsMessageHandler) {
this.formsMessageHandler = formsMessageHandler;
}
/**
* Set {@link #formResponseDao}
* @param formResponseDao new value for {@link #formResponseDao}
*/
@Required
public void setFormResponseDao(FormResponseDao formResponseDao) {
this.formResponseDao = formResponseDao;
}
/**
* Set {@link #formDao}
* @param formsDao new value for {@link #formDao}
*/
@Required
public void setFormsDao(FormDao formsDao) {
this.formDao = formsDao;
}
public void setContactDao(ContactDao contactDao) {
this.contactDao = contactDao;
}
/** @see net.frontlinesms.plugins.PluginController#deinit() */
public void deinit() {
this.frontlineController.removeIncomingMessageListener(this);
}
/** @see BasePluginController#initThinletTab(UiGeneratorController) */
public Object initThinletTab(UiGeneratorController uiController) {
FormsThinletTabController tabController = new FormsThinletTabController(this, uiController);
tabController.setContactDao(this.frontlineController.getContactDao());
tabController.setGroupMembershipDao(this.frontlineController.getGroupMembershipDao());
tabController.setFormsDao(formDao);
tabController.setFormResponseDao(formResponseDao);
Object formsTab = uiController.loadComponentFromFile(XML_FORMS_TAB, tabController);
tabController.setTabComponent(formsTab);
tabController.refresh();
return formsTab;
}
//> EVENT HANDLING METHODS
/** Process a new message coming into the system. */
public void incomingMessageEvent(FrontlineMessage message) {
try {
FormsRequestDescription request = this.formsMessageHandler.handleIncomingMessage(message);
FormsResponseDescription response;
if(request instanceof DataSubmissionRequest) {
response = handleDataSubmissionRequest((DataSubmissionRequest)request, message.getSenderMsisdn());
} else if(request instanceof NewFormRequest) {
response = handleNewFormRequest((NewFormRequest)request, message.getSenderMsisdn());
} else {
throw new IllegalStateException("Unknown form request description type: " + request);
}
// If there is a response to send back to the form submitter, then process it
if(response != null) {
handleResponse(request.getSmsPort(), message.getSenderMsisdn(), response);
}
} catch (Throwable t) {
log.info("There was a problem handling incoming message as forms message.", t);
}
}
/* default access to allow for unit test */
void handleResponse(Integer smsPort, String senderMsisdn, FormsResponseDescription response) throws FormHandlingException {
Collection<FrontlineMessage> responseMessages = this.formsMessageHandler.handleOutgoingMessage(response);
log.info("Sending forms response. Response messages: " + responseMessages.size());
for(FrontlineMessage responseMessage : responseMessages) {
// Make sure that the response is sent to the correct recipient!
responseMessage.setRecipientMsisdn(senderMsisdn);
if(smsPort != null) {
responseMessage.setRecipientSmsPort(smsPort);
}
this.frontlineController.sendMessage(responseMessage);
}
log.trace("Response messages sent.");
}
//> PRIVATE HELPER METHODS
/**
* Handles a request of type: {@link DataSubmissionRequest}
* @param request
* @param senderMsisdn
* @return a response of type {@link SubmittedDataResponse}
*/
/* default access to allow for unit test */
SubmittedDataResponse handleDataSubmissionRequest(DataSubmissionRequest request, String senderMsisdn) {
/** List of data IDs of the successfully processed responses */
Collection<SubmittedFormData> dataIds = new HashSet<SubmittedFormData>();
for(SubmittedFormData submittedData : request.getSubmittedData()) {
Form form = this.formDao.getFromId(submittedData.getFormId());
if(form == null) {
log.warn("No form found for submitted data with dataId: " + submittedData.getDataId());
continue;
}
List<ResponseValue> responseValues = submittedData.getDataValues();
if(form.getEditableFieldCount() != responseValues.size()) {
log.info("Editable field count mismatch: submitted " + responseValues.size() + "/" + form.getEditableFieldCount());
continue;
}
this.formResponseDao.saveResponse(new FormResponse(senderMsisdn, form, responseValues));
dataIds.add(submittedData);
}
return new SubmittedDataResponse(dataIds);
}
/**
* Handles a request of type {@link NewFormRequest}
* @param request
* @param message
* @return a response of type {@link NewFormsResponse}, or <code>null</code> if no response should be sent.
*/
/* default access to allow for unit test */
NewFormsResponse handleNewFormRequest(NewFormRequest request, String senderMsisdn) {
Contact contact = this.contactDao.getFromMsisdn(senderMsisdn);
if(contact == null || !contact.isActive()) {
// This contact is not known, so there cannot be any forms available for him
return null;
} else {
Collection<Form> newForms = this.formDao.getFormsForUser(contact, ((NewFormRequest)request).getCurrentFormIds());
return new NewFormsResponse(contact, newForms);
}
}
/**
* Send a form to a collection of contacts.
* @param form the form to send
* @param contacts the contacts to send the form to
*/
public void sendForm(Form form, Collection<Contact> contacts) {
// Send a text SMS to each contact informing them that a new form is available.
String messageContent = InternationalisationUtils.getI18NString(I18N_NEW_FORMS_SMS, form.getName());
for(Contact c : contacts) {
// TODO if it is possible, we could send forms directly to people here
this.frontlineController.sendTextMessage(c.getPhoneNumber(), messageContent);
}
}
/**
* @return The {@link FormDao of the plugin controller}
*/
public FormDao getFormDao() {
return formDao;
}
/**
* Get {@link #formResponseDao}
* @return FormResponseDao
*/
public FormResponseDao getFormResponseDao() {
return formResponseDao;
}
}