/**
  * Actually submits the selected translation
  *
  * @param userName The content of the User Name Field
  * @param userEmail The content of the User E-Mail address Field
  */
 public void submitTranslation(String userName, String userEmail, boolean contribute) {
   if (this.isValidEmailAddress(userEmail)) {
     this.ui.alert(InternationalisationUtils.getI18NString(I18N_COMMON_INVALID_EMAIL));
   } else {
     String subject = "FrontlineSMS translation: " + this.languageBundle.getLanguageName();
     String textContent =
         "Sent translation: "
             + this.languageBundle.getLanguageName()
             + " ("
             + this.languageBundle.getLanguageCode()
             + ").\n"
             + userName
             + (contribute ? " would" : " wouldn't")
             + " like to appear as a contributor for this translation.";
     try {
       FrontlineUtils.sendToFrontlineSupport(
           userName,
           userEmail,
           subject,
           textContent,
           InternationalisationUtils.getLanguageDirectory()
               + File.separator
               + languageBundle.getFilename());
       this.removeDialog();
       this.ui.infoMessage(InternationalisationUtils.getI18NString(I18N_TRANSLATION_SENT));
     } catch (EmailException e) {
       this.ui.alert(InternationalisationUtils.getI18NString(I18N_UNABLE_SEND_TRANSLATION));
     }
   }
 }
/*
 * RemindersFactory
 * @author Dale Zak
 *
 * see {@link "http://www.frontlinesms.net"} for more details.
 * copyright owned by Kiwanja.net
 */
public final class RemindersFactory {

  private static Logger LOG = FrontlineUtils.getLogger(RemindersFactory.class);

  /*
   * Get list of Reminder class implementations
   * (To add a new Reminder classes to the project, append a new row to the file
   * /resources/META-INF/services/net.frontlinesms.plugins.reminders.data.domain.Reminder
   * with the full package and class name of the new implementing Reminder class)
   */
  public static List<Reminder> getReminderClasses() {
    if (reminderClasses == null) {
      reminderClasses = new ArrayList<Reminder>();
      for (Reminder reminder : ServiceLoader.load(Reminder.class)) {
        LOG.debug("Reminder Discovered: " + reminder);
        reminderClasses.add(reminder);
      }
    }
    return reminderClasses;
  }

  private static List<Reminder> reminderClasses = null;

  /*
   * Create a Reminder according to it's occurrence
   */
  public static Reminder createReminder(
      long startdate,
      long enddate,
      Type type,
      String recipients,
      String subject,
      String content,
      String occurrence) {
    for (Reminder reminderClass : getReminderClasses()) {
      if (reminderClass.getOccurrence().equalsIgnoreCase(occurrence)) {
        try {
          Reminder newReminder = reminderClass.getClass().newInstance();
          newReminder.setStartDate(startdate);
          newReminder.setEndDate(enddate);
          newReminder.setType(type);
          newReminder.setRecipients(recipients);
          newReminder.setSubject(subject);
          newReminder.setContent(content);
          LOG.debug("Reminder Created: " + newReminder);
          return newReminder;
        } catch (InstantiationException e) {
          e.printStackTrace();
        } catch (IllegalAccessException e) {
          e.printStackTrace();
        }
      }
    }
    LOG.debug("Unable to find class for occurrence: " + occurrence);
    return null;
  }
}
/*
 * WeekdaysReminder
 * @author Dale Zak
 *
 * see {@link "http://www.frontlinesms.net"} for more details.
 * copyright owned by Kiwanja.net
 */
@Entity
@DiscriminatorValue(value = "weekdays")
public class WeekdaysReminder extends Reminder {

  private static final Logger LOG = FrontlineUtils.getLogger(WeekdaysReminder.class);

  public WeekdaysReminder() {}

  public WeekdaysReminder(
      long startdate, long enddate, Type type, String recipients, String subject, String content) {
    super(startdate, enddate, type, recipients, subject, content);
  }

  @Override
  public String getOccurrenceLabel() {
    return InternationalisationUtils.getI18nString(RemindersConstants.WEEKDAYS);
  }

  @Override
  public String getOccurrence() {
    return "weekdays";
  }

  public static boolean isSatisfiedBy(String occurrence) {
    return "weekdays".equalsIgnoreCase(occurrence);
  }

  public long getPeriod() {
    return 1000 * 60 * 60 * 24;
  }

  public void run() {
    LOG.debug("run: " + this);
    Calendar now = Calendar.getInstance();
    Calendar start = this.getStartCalendar();
    Calendar end = this.getEndCalendar();
    if (now.after(end)) {
      this.setStatus(Status.SENT);
      this.stopReminder();
      this.refreshReminder();
    } else if ((now.equals(start) || now.after(start))
        && now.get(Calendar.MINUTE) == start.get(Calendar.MINUTE)
        && now.get(Calendar.HOUR_OF_DAY) == start.get(Calendar.HOUR_OF_DAY)
        && Arrays.asList(WEEKDAYS).contains(now.get(Calendar.DAY_OF_WEEK))) {
      this.sendReminder();
    }
  }

  private static final int[] WEEKDAYS =
      new int[] {
        Calendar.MONDAY, Calendar.TUESDAY, Calendar.WEDNESDAY, Calendar.THURSDAY, Calendar.FRIDAY
      };
}
예제 #4
0
/** Base UI used for FrontlineSMS. */
@SuppressWarnings("serial")
public abstract class FrontlineUI extends ExtendedThinlet implements ThinletUiEventHandler {

  // > UI DEFINITION FILES
  /** Thinlet UI layout File: alert popup box */
  protected static final String UI_FILE_ALERT = "/ui/core/util/dgAlert.xml";
  /** Thinlet UI layout File: info popup box */
  protected static final String UI_FILE_INFO = "/ui/core/util/dgInfo.xml";

  // > UI COMPONENTS
  /** Component of {@link #UI_FILE_ALERT} which contains the message to display */
  private static final String COMPONENT_ALERT_MESSAGE = "alertMessage";
  /** Component of {@link #UI_FILE_INFO} which contains the message to display */
  private static final String COMPONENT_INFO_MESSAGE = "infoMessage";

  // > INSTANCE PROPERTIES
  /** Logging object */
  protected final Logger log = FrontlineUtils.getLogger(this.getClass());
  /**
   * The language bundle currently in use. N.B. THIS CAN BE NULL, IN WHICH CASE THE DEFAULT BUNDLE
   * SHOULD BE USED TODO handle and document this better
   */
  public static LanguageBundle currentResourceBundle;
  /**
   * Frame launcher that this UI instance is displayed within. We need to keep a handle on it so
   * that we can dispose of it when we quit or change UI modes.
   */
  protected FrameLauncher frameLauncher;

  /**
   * Gets the icon for a specific language bundle
   *
   * @param languageBundle
   * @return the flag image for the language bundle, or <code>null</code> if none could be found.
   */
  public Image getFlagIcon(LanguageBundle languageBundle) {
    return getFlagIcon(languageBundle.getCountry());
  }

  /**
   * Gets the icon for a specific country
   *
   * @param languageBundle
   * @return the flag image for the language bundle, or <code>null</code> if none could be found.
   */
  public Image getFlagIcon(String country) {
    String flagFile = country != null ? "/icons/flags/" + country + ".png" : null;
    return country == null ? null : getIcon(flagFile);
  }

  /**
   * Loads a Thinlet UI descriptor from an XML file. If there are any problems loading the file,
   * this will log Throwables thrown and allow the program to continue running.
   *
   * <p>{@link #loadComponentFromFile(String, Object)} should always be used by external handlers in
   * preference to this.
   *
   * @param filename path of the UI XML file to load from
   * @return thinlet component loaded from the file
   */
  public Object loadComponentFromFile(String filename) {
    return loadComponentFromFile(filename, this);
  }

  /**
   * Loads a Thinlet UI descriptor from an XML file and sets the provided event handler. If there
   * are any problems loading the file, this will log Throwables thrown and allow the program to
   * continue running.
   *
   * @param filename path of the UI XML file to load from
   * @param thinletEventHandler event handler for the UI component
   * @return thinlet component loaded from the file
   */
  public Object loadComponentFromFile(String filename, ThinletUiEventHandler thinletEventHandler) {
    log.trace("ENTER");
    try {
      log.debug("Filename [" + filename + "]");
      log.trace("EXIT");
      return parse(filename, thinletEventHandler);
    } catch (Throwable t) {
      log.error("Error parsing file [" + filename + "]", t);
      log.trace("EXIT");
      throw new RuntimeException(t);
    }
  }

  /**
   * This method opens a fileChooser.
   *
   * @param textFieldToBeSet The text field whose value should be sert to the chosen file
   */
  public void showFileChooser(Object textFieldToBeSet) {
    FileChooser.showFileChooser(this, textFieldToBeSet);
  }

  /**
   * Popup an alert to the user with the supplied message.
   *
   * @param alertMessage
   */
  public void alert(String alertMessage) {
    Object alertDialog = loadComponentFromFile(UI_FILE_ALERT);
    setText(find(alertDialog, COMPONENT_ALERT_MESSAGE), alertMessage);
    add(alertDialog);
  }

  /**
   * Popup an info message to the user with the supplied message.
   *
   * @param infoMessage
   */
  public void infoMessage(String infoMessage) {
    Object infoDialog = loadComponentFromFile(UI_FILE_INFO);
    setText(find(infoDialog, COMPONENT_INFO_MESSAGE), infoMessage);
    add(infoDialog);
  }

  /**
   * Removes the supplied dialog from the application.
   *
   * @param dialog
   */
  public void removeDialog(Object dialog) {
    remove(dialog);
  }

  /**
   * Opens a link in the system browser
   *
   * @param url the url to open
   * @see FrontlineUtils#openExternalBrowser(String)
   */
  public void openBrowser(String url) {
    FrontlineUtils.openExternalBrowser(url);
  }

  /**
   * Opens a page of the help manual
   *
   * @param page The name of the help manual page, including file extension.
   */
  public void showHelpPage(String page) {
    FrontlineUtils.openHelpPageInBrowser(page);
  }

  /** Shows an error dialog informing the user that an unhandled error has occurred. */
  @Override
  protected void handleException(Throwable throwable) {
    log.error("Unhandled exception from thinlet.", throwable);
    ErrorUtils.showErrorDialog(
        "Unexpected error", "There was an unexpected error.", throwable, false);
  }
}
예제 #5
0
 /**
  * Opens a page of the help manual
  *
  * @param page The name of the help manual page, including file extension.
  */
 public void showHelpPage(String page) {
   FrontlineUtils.openHelpPageInBrowser(page);
 }
예제 #6
0
 /**
  * Opens a link in the system browser
  *
  * @param url the url to open
  * @see FrontlineUtils#openExternalBrowser(String)
  */
 public void openBrowser(String url) {
   FrontlineUtils.openExternalBrowser(url);
 }
/**
 * Base properties wrapper class.
 *
 * @author Alex
 */
class BasePropertySet {
  // > STATIC CONSTANTS
  /** Logging object for this instance. */
  public static final Logger LOG = FrontlineUtils.getLogger(BasePropertySet.class);

  // > INSTANCE PROPERTIES
  /** Map from property key to value */
  private Map<String, String> properties;

  // > CONSTRUCTORS

  // > ACCESSORS
  /**
   * @param propertyKey The key for the property to get
   * @return value from {@link #properties}
   */
  String getProperty(String propertyKey) {
    return this.properties.get(propertyKey);
  }

  Map<String, String> getProperties() {
    return properties;
  }

  /**
   * Set the properties. This should be done exactly once.
   *
   * @param properties value for {@link #properties}
   */
  protected void setProperties(Map<String, String> properties) {
    assert (this.properties == null) : "Properties already set.  Cannot be changed.";
    assert (properties != null) : "Cannot set properties to null.";
    this.properties = properties;
  }

  // > INSTANCE HELPER METHODS

  // > STATIC FACTORIES

  // > STATIC HELPER METHODS
  /**
   * Load properties from an {@link InputStream}.
   *
   * @param inputStream The input stream to load the properties from.
   * @return The loaded properties
   * @throws IOException If there was a problem loading the properties from the supplied {@link
   *     InputStream}.
   */
  static void load(Map<String, String> map, InputStream inputStream) throws IOException {
    if (inputStream == null) throw new NullPointerException("The supplied input stream was null.");

    BufferedReader in = null;
    try {
      in = new BufferedReader(new InputStreamReader(inputStream, "UTF-8"));

      String line;
      while ((line = in.readLine()) != null) {
        line = line.trim();
        if (line.length() == 0 || line.charAt(0) == '#') {
          // This is a comment, so we should remember it to write back later
        } else {
          int splitChar = line.indexOf('=');
          if (splitChar <= 0) {
            // there's no "key=value" pair on this line, but it does have text on it.  That's
            // not strictly legal, so we'll log a warning and carry on.
            LOG.warn("Bad line in properties file: '" + line + "'");
          } else {
            String key = line.substring(0, splitChar);
            if (map.containsKey(key)) {
              // This key has already been read from the language file.  Ignore the new value.
              LOG.warn("Duplicate key in properties file: ''");
            } else {
              String value = line.substring(splitChar + 1);
              map.put(key, value);
            }
          }
        }
      }

      LOG.trace("EXIT");
    } finally {
      // Close all streams
      if (in != null)
        try {
          in.close();
        } catch (IOException ex) {
          // nothing we can do except log the exception
          LOG.warn("Exception thrown while closing stream 'fis'.", ex);
        }
    }
  }

  static HashMap<String, String> load(InputStream inputStream) throws IOException {
    HashMap<String, String> map = new HashMap<String, String>();
    load(map, inputStream);
    return map;
  }
}
public class IncomingFormMatcher implements EventObserver {

  private static Logger LOG = FrontlineUtils.getLogger(MedicFormResponse.class);

  private Levenshtein levenshtein;
  private JaroWinkler jaroWinkler;

  private FormDao vanillaFormDao;
  private MedicFormDao formDao;
  private MedicFormResponseDao formResponseDao;
  private MedicFormFieldResponseDao formFieldResponseDao;
  private PatientDao patientDao;
  private CommunityHealthWorkerDao chwDao;

  private SimpleDateFormat shortFormatter;
  private DateFormat longFormatter;

  public IncomingFormMatcher(ApplicationContext appCon) {
    vanillaFormDao = (FormDao) appCon.getBean("formDao");
    formDao = (MedicFormDao) appCon.getBean("MedicFormDao");
    formResponseDao = (MedicFormResponseDao) appCon.getBean("MedicFormResponseDao");
    formFieldResponseDao = (MedicFormFieldResponseDao) appCon.getBean("MedicFormFieldResponseDao");
    patientDao = (PatientDao) appCon.getBean("PatientDao");
    chwDao = (CommunityHealthWorkerDao) appCon.getBean("CHWDao");
    ((EventBus) appCon.getBean("eventBus")).registerObserver(this);
    // create the test harness
    ExtendedThinlet thinlet = new ExtendedThinlet();
    Object panel = thinlet.createPanel("mainPanel");
    thinlet.setWeight(panel, 1, 1);
    Object button = thinlet.createButton("Click me to test form handling");
    thinlet.setAction(button, "testHandler", null, this);
    thinlet.add(panel, button);
    thinlet.add(panel);
    // set up the date formatter
    String dateString =
        InternationalisationUtils.getI18NString(FrontlineSMSConstants.DATEFORMAT_YMD);
    dateString = dateString.toLowerCase();
    dateString = dateString.replace("mm", "MM");
    dateString = dateString.replace("yyyy", "yy");
    shortFormatter = new SimpleDateFormat(dateString);
    longFormatter = InternationalisationUtils.getDateFormat();
    //	FrameLauncher f = new FrameLauncher("Test form handling",thinlet,200,100,null)
    // { public void windowClosing(WindowEvent e){  dispose(); }};
  }

  public void testHandler() {
    List<ResponseValue> responses = new ArrayList<ResponseValue>();
    responses.add(new ResponseValue("Frankie Tenday"));
    responses.add(new ResponseValue("16/06/1964"));
    responses.add(new ResponseValue("First answer"));
    responses.add(new ResponseValue("true"));
    responses.add(new ResponseValue("false"));
    responses.add(new ResponseValue("true"));
    responses.add(new ResponseValue("false"));
    responses.add(new ResponseValue("true"));
    responses.add(new ResponseValue("false"));
    responses.add(new ResponseValue("true"));
    responses.add(new ResponseValue("false"));
    responses.add(new ResponseValue("true"));
    responses.add(new ResponseValue("false"));
    responses.add(new ResponseValue("true"));
    responses.add(new ResponseValue("false"));
    responses.add(new ResponseValue("true"));
    responses.add(new ResponseValue("false"));
    responses.add(new ResponseValue("true"));
    responses.add(new ResponseValue("false"));
    responses.add(new ResponseValue("true"));
    responses.add(new ResponseValue("false"));
    responses.add(new ResponseValue("true"));
    responses.add(new ResponseValue("false"));
    responses.add(new ResponseValue("true"));
    FormResponse fr = new FormResponse("2099707079", vanillaFormDao.getFromId(10L), responses);
    handleFormResponse(fr);
  }

  public boolean isMedicForm(Form f) {
    return formDao.getMedicFormForForm(f) != null;
  }

  /**
   * When a form is submitted, this method attempts to pair that form with the patient that is its
   * subject. If there is more than one possibility, the form is posted to the changelog with a list
   * of suggested patients. If there is only one real possibility, but that possibility does not
   * match exactly, it is also posted to the changelog with a snippet about what did not match and
   * what did.
   *
   * @param formResponse
   */
  public void handleFormResponse(FormResponse formResponse) {
    // if the form submitted is not a medic form, then do nothing
    if (!isMedicForm(formResponse.getParentForm())) {
      return;
    }
    // get the medic form equivalent of the form submitted
    MedicForm mForm = formDao.getMedicFormForForm(formResponse.getParentForm());
    CommunityHealthWorker submitter =
        chwDao.getCommunityHealthWorkerByPhoneNumber(formResponse.getSubmitter());
    MedicFormResponse mfr = new MedicFormResponse(formResponse, mForm, submitter, null);
    mfr.setSubject(getFinalCandidate(mfr));
    formResponseDao.saveMedicFormResponse(mfr);
  }

  /**
   * Returns a float from 1.0 - 0.0 that measures the similarity between 2 strings (mainly names)
   * using the jaro-winkler method. The higher the number, the greater the similarity
   *
   * @param patientName string 1 (generally the name of the patient)
   * @param responseName string 2 (generally the name typed into the form
   * @return a float from 1.0 - 0.0
   */
  private float getNameDistance(String patientName, String responseName) {
    if (jaroWinkler == null) {
      jaroWinkler = new JaroWinkler();
    }
    return jaroWinkler.getSimilarity(patientName, responseName);
  }

  /**
   * Returns the edit distance between 2 strings, as implemented by Levenshtein
   *
   * @param stringOne
   * @param stringTwo
   * @return a value from 0.0 -1.0. The greater the similarity, the higher the number
   */
  public float getEditDistance(String stringOne, String stringTwo) {
    if (levenshtein == null) {
      levenshtein = new Levenshtein();
    }
    return levenshtein.getSimilarity(stringOne, stringTwo);
  }

  public List<Candidate> getCandidatesForResponse(MedicFormResponse response) {
    Log.info("Attempting to map response");
    // get the CHW that submitted the form
    CommunityHealthWorker chw = (CommunityHealthWorker) response.getSubmitter();
    // get the list of patients that the CHW cares for
    ArrayList<Patient> patients = (ArrayList<Patient>) patientDao.getPatientsForCHW(chw);
    ArrayList<Candidate> candidates = new ArrayList<Candidate>();
    // iterate through all fields on the form, seeing if they are mapped to patient identifying
    // fields
    // e.g. Birthdate, Name, and Patient ID
    for (Patient patient : patients) {
      candidates.add(new Candidate(patient));
    }
    List<MedicFormFieldResponse> responses = response.getResponses();
    try {
      responses.get(0).getDateSubmitted();
    } catch (Exception e) {
      responses = formFieldResponseDao.getResponsesForFormResponse(response);
    }

    for (MedicFormFieldResponse fieldResponse : responses) {
      // if it is mapped to a namefield, score it as a name
      if (fieldResponse.getField().getMapping() == PatientFieldMapping.NAMEFIELD) {
        for (Candidate c : candidates) {
          c.setNameScore(
              getNameDistance(c.getName().toLowerCase(), fieldResponse.getValue().toLowerCase()));
        }
        // if it is mapped to an id field, score it as an ID
      } else if (fieldResponse.getField().getMapping() == PatientFieldMapping.IDFIELD) {
        for (Candidate c : candidates) {
          c.setIdScore(getEditDistance(c.getStringID(), fieldResponse.getValue()));
        }
        // if it is mapped as a bday field, score it as a bday
      } else if (fieldResponse.getField().getMapping() == PatientFieldMapping.BIRTHDATEFIELD) {
        for (Candidate c : candidates) {
          if (fieldResponse.getValue().length() <= 8) {
            c.setBirthdateScore(
                getEditDistance(
                    shortFormatter.format(c.getPatient().getBirthdate()),
                    fieldResponse.getValue()));
          } else {
            c.setBirthdateScore(
                getEditDistance(
                    longFormatter.format(c.getPatient().getBirthdate()), fieldResponse.getValue()));
          }
        }
      }
    }
    Collections.sort(candidates);
    return candidates.subList(0, 5);
  }

  public float getConfidence(Patient subject, MedicFormResponse mfr) {
    List<MedicFormFieldResponse> responses = formFieldResponseDao.getResponsesForFormResponse(mfr);
    float result = 0.0F;
    float total = 0.0F;
    for (MedicFormFieldResponse fieldResponse : responses) {
      // if it is mapped to a namefield, score it as a name
      if (fieldResponse.getField().getMapping() == PatientFieldMapping.NAMEFIELD) {
        result += getNameDistance(subject.getName(), fieldResponse.getValue());
        total += 1.0F;
        // if it is mapped to an id field, score it as an ID
      } else if (fieldResponse.getField().getMapping() == PatientFieldMapping.IDFIELD) {
        result += getEditDistance(subject.getStringID(), fieldResponse.getValue());
        total += 1.0F;
        // if it is mapped as a bday field, score it as a bday
      } else if (fieldResponse.getField().getMapping() == PatientFieldMapping.BIRTHDATEFIELD) {
        if (fieldResponse.getValue().length() <= 8) {
          result +=
              getEditDistance(
                  shortFormatter.format(subject.getBirthdate()), fieldResponse.getValue());
        } else {
          result +=
              getEditDistance(
                  longFormatter.format(subject.getBirthdate()), fieldResponse.getValue());
        }
        total += 1.0F;
      }
    }
    return (result / total) * 100;
  }

  /**
   * Returns the 'final candidate', i.e. the most likely candidate for the subject of the supplied
   * form response. This is determined by fetching all candidates, and selecting the ones that are
   * over 97% confidence. If there is only one over 97% confidence, then that candidate is returned.
   * Otherwise, this method returns null
   *
   * @param response
   * @return
   */
  public Person getFinalCandidate(MedicFormResponse response) {
    List<Candidate> candidates = getCandidatesForResponse(response);
    List<Candidate> finalCandidates = new ArrayList<Candidate>();
    for (Candidate c : candidates) {
      if (c.getAverageScore() >= 97F) {
        finalCandidates.add(c);
      }
    }
    if (finalCandidates.size() == 1) {
      return finalCandidates.get(0).getPatient();
    }
    return null;
  }

  public void notify(FrontlineEventNotification notification) {
    if (notification instanceof EntitySavedNotification<?>) {
      EntitySavedNotification<?> sNotification = (EntitySavedNotification<?>) notification;
      if (sNotification.getDatabaseEntity() instanceof FormResponse) {
        handleFormResponse((FormResponse) sNotification.getDatabaseEntity());
      }
    }
  }
}