diff --git a/opensrp-connector/pom.xml b/opensrp-connector/pom.xml
index bc38e8b384..02c9212004 100644
--- a/opensrp-connector/pom.xml
+++ b/opensrp-connector/pom.xml
@@ -190,6 +190,16 @@
atomfeed-client
1.9.1
+
+ org.bouncycastle
+ bcprov-jdk15on
+ 1.54
+
+
+ org.bouncycastle
+ bcprov-ext-jdk15on
+ 1.54
+
diff --git a/opensrp-connector/src/main/java/org/opensrp/connector/HttpUtil.java b/opensrp-connector/src/main/java/org/opensrp/connector/HttpUtil.java
index c078618e79..bd13bb00a1 100644
--- a/opensrp-connector/src/main/java/org/opensrp/connector/HttpUtil.java
+++ b/opensrp-connector/src/main/java/org/opensrp/connector/HttpUtil.java
@@ -7,10 +7,25 @@
import java.io.PrintWriter;
import java.net.HttpURLConnection;
import java.net.URL;
+import java.net.URLConnection;
+import java.security.KeyManagementException;
+import java.security.NoSuchAlgorithmException;
+import java.security.Security;
+import java.security.cert.CertificateException;
+import java.security.cert.X509Certificate;
+
+import javax.net.ssl.HostnameVerifier;
+import javax.net.ssl.HttpsURLConnection;
+import javax.net.ssl.SSLContext;
+import javax.net.ssl.SSLSession;
+import javax.net.ssl.SSLSocketFactory;
+import javax.net.ssl.TrustManager;
+import javax.net.ssl.X509TrustManager;
import org.apache.commons.codec.binary.Base64;
import org.apache.commons.httpclient.HttpStatus;
import org.apache.commons.io.IOUtils;
+import org.bouncycastle.jce.provider.BouncyCastleProvider;
import org.opensrp.common.util.HttpResponse;
import org.springframework.http.HttpMethod;
import org.springframework.stereotype.Component;
@@ -38,7 +53,7 @@ public HttpUtil() {
*/
public static HttpResponse post(String url, String payload, String data, String username,String password) {
try {
- HttpURLConnection con = makeConnection(url, payload, HttpMethod.POST, true, username, password);
+ HttpsURLConnection con = makeConnection(url, payload, HttpMethod.POST, true, username, password);
con.setDoOutput(true);
con.setRequestProperty("Content-Type", "application/json");
String charset = "utf-8";
@@ -66,7 +81,7 @@ public static HttpResponse post(String url, String payload, String data, String
*/
public static HttpResponse get(String url, String payload, String username, String password) {
try {
- HttpURLConnection con = makeConnection(url, payload, HttpMethod.GET, true, username, password);
+ HttpsURLConnection con = makeConnection(url, payload, HttpMethod.GET, true, username, password);
System.out.println(url);
HttpResponse resp = new HttpResponse(con.getResponseCode() == HttpStatus.SC_OK, IOUtils.toString(con.getInputStream()));
System.out.println(resp);
@@ -83,7 +98,7 @@ public static HttpResponse get(String url, String payload, String username, Stri
}
}
- static HttpURLConnection makeConnection(String url, String payload, HttpMethod requestMethod, boolean useBasicAuth, String username, String password) throws IOException {
+ static HttpsURLConnection makeConnection(String url, String payload, HttpMethod requestMethod, boolean useBasicAuth, String username, String password) throws IOException {
String charset = "UTF-8";
if(url.endsWith("/")){
@@ -91,7 +106,7 @@ static HttpURLConnection makeConnection(String url, String payload, HttpMethod r
}
url = (url+(StringUtils.isEmptyOrWhitespaceOnly(payload)?"":("?"+payload))).replaceAll(" ", "%20");
URL urlo = new URL(url);
- HttpURLConnection conn = (HttpURLConnection) urlo.openConnection();
+ HttpsURLConnection conn = (HttpsURLConnection) urlo.openConnection();
conn.setRequestProperty("Accept-Charset", charset);
if(useBasicAuth){
@@ -110,4 +125,51 @@ public static String removeEndingSlash(String str){
public static String removeTrailingSlash(String str){
return str.startsWith("/")?str.substring(1):str;
}
+
+ static {
+ disableSslVerification();
+ }
+
+ private static void disableSslVerification() {
+ System.setProperty("disable_bad_sslciphers", "yes");
+ System.setProperty("jsse.enableSNIExtension", "false");
+ Security.addProvider(new BouncyCastleProvider());
+
+ try
+ {
+ // Create a trust manager that does not validate certificate chains
+ TrustManager[] trustAllCerts = new TrustManager[] {new X509TrustManager() {
+ public java.security.cert.X509Certificate[] getAcceptedIssuers() {
+ return null;
+ }
+ @Override
+ public void checkClientTrusted(X509Certificate[] arg0, String arg1) throws CertificateException {
+ }
+ @Override
+ public void checkServerTrusted(X509Certificate[] arg0, String arg1) throws CertificateException {
+ }
+ }
+ };
+
+ // Install the all-trusting trust manager
+ SSLContext sc = SSLContext.getInstance("TLS");
+ sc.init(null, trustAllCerts, new java.security.SecureRandom());
+ SSLSocketFactory sf = sc.getSocketFactory();
+ HttpsURLConnection.setDefaultSSLSocketFactory(new SecureSocketFactory(sf));
+
+ // Create all-trusting host name verifier
+ HostnameVerifier allHostsValid = new HostnameVerifier() {
+ public boolean verify(String hostname, SSLSession session) {
+ return true;
+ }
+ };
+
+ // Install the all-trusting host verifier
+ HttpsURLConnection.setDefaultHostnameVerifier(allHostsValid);
+ } catch (NoSuchAlgorithmException e) {
+ e.printStackTrace();
+ } catch (KeyManagementException e) {
+ e.printStackTrace();
+ }
+ }
}
\ No newline at end of file
diff --git a/opensrp-connector/src/main/java/org/opensrp/connector/SecureSocketFactory.java b/opensrp-connector/src/main/java/org/opensrp/connector/SecureSocketFactory.java
new file mode 100644
index 0000000000..6d23292df1
--- /dev/null
+++ b/opensrp-connector/src/main/java/org/opensrp/connector/SecureSocketFactory.java
@@ -0,0 +1,75 @@
+package org.opensrp.connector;
+
+import java.io.IOException;
+import java.net.InetAddress;
+import java.net.Socket;
+import java.net.UnknownHostException;
+import java.util.LinkedList;
+import java.util.List;
+
+import javax.net.ssl.SSLSocket;
+import javax.net.ssl.SSLSocketFactory;
+
+public class SecureSocketFactory extends SSLSocketFactory {
+
+ private final SSLSocketFactory delegate;
+
+ public SecureSocketFactory(SSLSocketFactory delegate) {
+
+ this.delegate = delegate;
+ }
+
+ @Override
+ public String[] getDefaultCipherSuites() {
+
+ return this.delegate.getDefaultCipherSuites();
+ }
+
+ @Override
+ public String[] getSupportedCipherSuites() {
+
+ return this.delegate.getSupportedCipherSuites();
+ }
+
+ @Override
+ public Socket createSocket(String arg0, int arg1) throws IOException, UnknownHostException {
+ Socket socket = this.delegate.createSocket(arg0, arg1);
+ return handleSocket(socket);
+ }
+
+ private Socket handleSocket(Socket socket){
+ List limited = new LinkedList();
+ for (String suite : ((SSLSocket) socket).getEnabledCipherSuites()) {
+ if (!suite.contains("_ECDHE_") && !suite.contains("_DH_") && !suite.contains("_DHE_")) {
+ limited.add(suite);
+ }
+ }
+ ((SSLSocket) socket).setEnabledCipherSuites(limited.toArray(new String[limited.size()]));
+ return socket;
+ }
+
+ @Override
+ public Socket createSocket(InetAddress arg0, int arg1) throws IOException {
+ Socket socket = this.delegate.createSocket(arg0, arg1);
+ return handleSocket(socket);
+ }
+
+ @Override
+ public Socket createSocket(Socket arg0, String arg1, int arg2, boolean arg3) throws IOException {
+ Socket socket = this.delegate.createSocket(arg0, arg1, arg2, arg3);
+ return handleSocket(socket);
+ }
+
+ @Override
+ public Socket createSocket(String arg0, int arg1, InetAddress arg2, int arg3) throws IOException, UnknownHostException {
+ Socket socket = this.delegate.createSocket(arg0, arg1, arg2, arg3);
+ return handleSocket(socket);
+ }
+
+ @Override
+ public Socket createSocket(InetAddress arg0, int arg1, InetAddress arg2, int arg3) throws IOException {
+ Socket socket = this.delegate.createSocket(arg0, arg1, arg2, arg3);
+ return handleSocket(socket);
+ }
+
+}
\ No newline at end of file
diff --git a/opensrp-core/src/main/java/org/opensrp/service/EventService.java b/opensrp-core/src/main/java/org/opensrp/service/EventService.java
index b15827ef17..715ec853d9 100644
--- a/opensrp-core/src/main/java/org/opensrp/service/EventService.java
+++ b/opensrp-core/src/main/java/org/opensrp/service/EventService.java
@@ -66,6 +66,7 @@ public synchronized Event addEvent(Event event)
}
event.setDateCreated(new Date());
+ event.setDateEdited(new Date());
if(StringUtils.isEmptyOrWhitespaceOnly(event.getEventId())){
event.setEventId(System.currentTimeMillis()+"");
}
diff --git a/opensrp-core/src/main/java/org/opensrp/service/XlsFormDownloaderService.java b/opensrp-core/src/main/java/org/opensrp/service/XlsFormDownloaderService.java
index c19b08ab26..e9d72a6498 100644
--- a/opensrp-core/src/main/java/org/opensrp/service/XlsFormDownloaderService.java
+++ b/opensrp-core/src/main/java/org/opensrp/service/XlsFormDownloaderService.java
@@ -41,6 +41,8 @@ public static void main(String[] args) {
// } catch (IOException e) {
// e.printStackTrace();
// }
+
+ System.out.println(DateTime.now().toString("dd-MM-yyyy"));
}
public boolean downloadFormFiles(String directory,String username ,String formPath, String password,String formId, String formPk) throws IOException{
diff --git a/opensrp-core/src/main/java/org/opensrp/service/autosys/DataUpdateListener.java b/opensrp-core/src/main/java/org/opensrp/service/autosys/DataUpdateListener.java
new file mode 100644
index 0000000000..83b3b5c486
--- /dev/null
+++ b/opensrp-core/src/main/java/org/opensrp/service/autosys/DataUpdateListener.java
@@ -0,0 +1,115 @@
+package org.opensrp.service.autosys;
+
+import static java.text.MessageFormat.format;
+import static java.util.Collections.sort;
+import static org.apache.commons.lang.exception.ExceptionUtils.getFullStackTrace;
+
+import java.text.MessageFormat;
+import java.util.Comparator;
+import java.util.Date;
+import java.util.List;
+import java.util.concurrent.locks.ReentrantLock;
+
+import org.joda.time.DateTime;
+import org.motechproject.scheduler.domain.MotechEvent;
+import org.motechproject.server.event.annotations.MotechListener;
+import org.opensrp.common.AllConstants;
+import org.opensrp.domain.AppStateToken;
+import org.opensrp.domain.ErrorTrace;
+import org.opensrp.domain.Event;
+import org.opensrp.dto.form.FormSubmissionDTO;
+import org.opensrp.form.domain.FormSubmission;
+import org.opensrp.form.service.FormSubmissionService;
+import org.opensrp.service.ClientService;
+import org.opensrp.service.ConfigService;
+import org.opensrp.service.ErrorTraceService;
+import org.opensrp.service.EventService;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Component;
+
+import com.google.gson.Gson;
+import com.google.gson.reflect.TypeToken;
+
+@Component
+public class DataUpdateListener {
+ private static Logger logger = LoggerFactory.getLogger(DataUpdateListener.class.toString());
+ private static final ReentrantLock lock = new ReentrantLock();
+ private ClientService clientService;
+ private EventService eventService;
+ private ConfigService configService;
+ private FormSubmissionProcessor fsp;
+ private ErrorTraceService errorTraceService;
+
+ @Autowired
+ public DataUpdateListener(ClientService clientService, EventService eventService,
+ FormSubmissionProcessor fsp,
+ ConfigService configService, ErrorTraceService errorTraceService) {
+ this.clientService = clientService;
+ this.eventService = eventService;
+ this.configService = configService;
+ this.errorTraceService = errorTraceService;
+ this.fsp = fsp;
+ this.configService.registerAppStateToken(AllConstants.Config.FORM_ENTITY_PARSER_LAST_SYNCED_FORM_SUBMISSION,
+ 0, "Token to keep track of forms processed for client n event parsing and schedule handling", true);
+ }
+
+ @MotechListener(subjects = AllConstants.FORM_SCHEDULE_SUBJECT)
+ public void parseForms(MotechEvent event) {
+ if (!lock.tryLock()) {
+ logger.warn("Not fetching forms from Message Queue. It is already in progress.");
+ return;
+ }
+ try {
+ logger.info("Fetching Forms");
+ long version = getVersion();
+
+ List events = eventService.findEventsBy(null, null, null, null, null, null, null, new DateTime(version) , DateTime.now());
+ if (events.isEmpty()) {
+ logger.info("No new events found. Export token: " + version);
+ return;
+ }
+
+ logger.info(format("Fetched {0} new forms found. Export token: {1}", events.size(), version));
+
+ sort(events, serverVersionComparator());
+
+ for (Event e : events) {
+ try{
+ logger.info(format("Invoking save for form with instance Id: {0} and for entity Id: {1}", submission.instanceId(), submission.entityId()));
+
+ if(submission.getField("no_client_event") == null || submission.getField("no_client_event").contains("false")){
+ fsp.processFormSubmission(submission);
+ }
+
+ configService.updateAppStateToken(AllConstants.Config.FORM_ENTITY_PARSER_LAST_SYNCED_FORM_SUBMISSION, submission.serverVersion());
+ }
+ catch(Exception e){
+ e.printStackTrace();
+ errorTraceService.addError(new ErrorTrace(new Date(), "FormSubmissionProcessor", this.getClass().getName(), e.getStackTrace().toString(), "unsolved", FormSubmission.class.getName()));
+ }
+ }
+ } catch (Exception e) {
+ logger.error(MessageFormat.format("{0} occurred while trying to fetch forms. Message: {1} with stack trace {2}",
+ e.toString(), e.getMessage(), getFullStackTrace(e)));
+ } finally {
+ lock.unlock();
+ }
+ }
+
+ private long getVersion() {
+ AppStateToken token = configService.getAppStateTokenByName(AllConstants.Config.FORM_ENTITY_PARSER_LAST_SYNCED_FORM_SUBMISSION);
+ return token==null?0L:token.longValue();
+ }
+
+ private Comparator serverVersionComparator() {
+ return new Comparator() {
+ public int compare(Event first, Event second) {
+ long firstTimestamp = first.getDateCreated().getTime();
+ long secondTimestamp = second.getDateCreated().getTime();
+ return firstTimestamp == secondTimestamp ? 0 : firstTimestamp < secondTimestamp ? -1 : 1;
+ }
+ };
+ }
+}
diff --git a/opensrp-core/src/main/java/org/opensrp/service/autosys/FormSubmissionProcessor.java b/opensrp-core/src/main/java/org/opensrp/service/autosys/FormSubmissionProcessor.java
new file mode 100644
index 0000000000..53fd333362
--- /dev/null
+++ b/opensrp-core/src/main/java/org/opensrp/service/autosys/FormSubmissionProcessor.java
@@ -0,0 +1,159 @@
+package org.opensrp.service.autosys;
+
+import java.io.IOException;
+import java.text.ParseException;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import org.joda.time.LocalDate;
+import org.json.JSONException;
+import org.json.JSONObject;
+import org.opensrp.domain.Client;
+import org.opensrp.domain.Event;
+import org.opensrp.form.domain.FormSubmission;
+import org.opensrp.form.domain.SubFormData;
+import org.opensrp.scheduler.HealthSchedulerService;
+import org.opensrp.scheduler.Schedule;
+import org.opensrp.scheduler.Schedule.ActionType;
+import org.opensrp.service.ClientService;
+import org.opensrp.service.EventService;
+import org.opensrp.service.formSubmission.handler.FormSubmissionRouter;
+import org.opensrp.service.formSubmission.ziggy.ZiggyService;
+import org.opensrp.util.Utils;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+
+import com.google.gson.Gson;
+import com.mysql.jdbc.StringUtils;
+
+@Service
+public class FormSubmissionProcessor{
+ private static Logger logger = LoggerFactory.getLogger(DataUpdateListener.class.toString());
+
+ private ZiggyService ziggyService;
+ private FormSubmissionRouter formSubmissionRouter;
+ private FormEntityConverter formEntityConverter;
+ private ClientService clientService;
+ private EventService eventService;
+ private HealthSchedulerService scheduleService;
+
+ @Autowired
+ public FormSubmissionProcessor(ZiggyService ziggyService, FormSubmissionRouter formSubmissionRouter,
+ FormEntityConverter formEntityConverter, HealthSchedulerService scheduleService,
+ ClientService clientService, EventService eventService) throws IOException {
+ this.ziggyService = ziggyService;
+ this.formSubmissionRouter = formSubmissionRouter;
+ this.formEntityConverter = formEntityConverter;
+ this.scheduleService = scheduleService;
+ this.clientService = clientService;
+ this.eventService = eventService;
+ }
+
+ public void processFormSubmission(FormSubmission submission) throws Exception {
+ // ugly hack TODO
+ if(submission.bindType().equalsIgnoreCase("stock")) return;
+
+ // parse and into client and event model
+ logger.info("Creating model entities");
+ makeModelEntities(submission);
+ logger.info("Handling xls configured schedules");
+ handleSchedules(submission);
+ if(ziggyService.isZiggyCompliant(submission.bindType())){
+ passToZiggy(submission);
+ //and skip form submission routing as ziggy does it automatically
+ }
+ else {//if not ziggy entity call custom route handler explicitly
+ logger.info("Routing to custom handler");
+ formSubmissionRouter.route(submission);
+ }
+ }
+
+ void handleSchedules(FormSubmission submission) throws JSONException, IOException {
+ List schl = scheduleService.findAutomatedSchedules(submission.formName());
+ for (Schedule sch : schl) {
+ Map entsch = getEntitiesQualifyingForSchedule(submission, sch);
+ System.out.println("creating schedule for : "+entsch);
+ for (String enid : entsch.keySet()) {
+ if(sch.action().equals(ActionType.enroll)){
+ scheduleService.enrollIntoSchedule(enid, sch.schedule(),
+ sch.milestone(), entsch.get(enid), submission.instanceId());
+ }
+ else if(sch.action().equals(ActionType.fulfill)){
+ scheduleService.fullfillMilestoneAndCloseAlert(enid, submission.anmId(), sch.schedule()
+ , LocalDate.parse(entsch.get(enid)), submission.instanceId());
+ }
+ else if(sch.action().equals(ActionType.unenroll)){
+ scheduleService.unEnrollFromSchedule(enid, submission.anmId(), sch.schedule(), submission.instanceId());
+ }
+ else if(sch.action().equals(ActionType.unenroll) && sch.schedule().equalsIgnoreCase("*")){
+ scheduleService.unEnrollFromAllSchedules(enid, submission.instanceId());
+ }
+ }
+ }
+ }
+
+ Map getEntitiesQualifyingForSchedule(FormSubmission submission, Schedule schedule) throws JSONException {
+ Map entityIds = new HashMap();
+ if(schedule.applicableForEntity(submission.bindType())){
+ String res = evaluateScheduleFor(schedule, submission.instance().form().getFieldsAsMap());
+ if(!StringUtils.isEmptyOrWhitespaceOnly(res)){
+ entityIds.put(submission.entityId(), res);
+ }
+ }
+
+ if(submission.subForms() != null)
+ for (SubFormData sbf : submission.subForms()) {
+ if(schedule.applicableForEntity(sbf.bindType())){
+ for (Map inst : sbf.instances()) {
+ String res = evaluateScheduleFor(schedule, inst);
+ if(!StringUtils.isEmptyOrWhitespaceOnly(res)){
+ entityIds.put(inst.get("id"), res);
+ }
+ }
+ }
+ }
+ return entityIds;
+ }
+
+ String evaluateScheduleFor(Schedule schedule, Map flvl) {
+ //find first field in submission that qualifies triggerdate field and has a value
+ for (String tf : schedule.triggerDateFields()) {
+ String flv = flvl.get(tf);
+ // if field has value and schedule flag field is empty or has value 1 or true
+ if(!StringUtils.isEmptyOrWhitespaceOnly(flv) && schedule.passesValidations(flvl)){
+ return flv;
+ }
+ }
+ return null;
+ }
+
+ private void makeModelEntities(FormSubmission submission) throws JSONException {
+ Client c = formEntityConverter.getClientFromFormSubmission(submission);
+ Event e = formEntityConverter.getEventFromFormSubmission(submission);
+ Map> dep = formEntityConverter.getDependentClientsFromFormSubmission(submission);
+
+ if(clientService.findClient(c) != null){
+ clientService.mergeClient(c);
+ }
+ else clientService.addClient(c);
+
+ eventService.addEvent(e);
+ // TODO relationships b/w entities
+
+ for (Map cm : dep.values()) {
+ Client cin = (Client)cm.get("client");
+ Event evin = (Event)cm.get("event");
+ clientService.addClient(cin);
+ eventService.addEvent(evin);
+ }
+ }
+
+ private void passToZiggy(FormSubmission submission) {
+ String params = Utils.getZiggyParams(submission);
+ ziggyService.saveForm(params, new Gson().toJson(submission.instance()));
+ }
+
+}
diff --git a/opensrp-core/src/test/java/org/opensrp/scheduler/FormEventListenerTest.java b/opensrp-core/src/test/java/org/opensrp/scheduler/FormEventListenerTest.java
index d5ec2b8d05..8fa47f8308 100644
--- a/opensrp-core/src/test/java/org/opensrp/scheduler/FormEventListenerTest.java
+++ b/opensrp-core/src/test/java/org/opensrp/scheduler/FormEventListenerTest.java
@@ -4,11 +4,11 @@
import static org.mockito.Mockito.verify;
import static org.mockito.Matchers.*;
import static org.mockito.Mockito.*;
-import static org.mockito.Mockito.when;
import static org.mockito.MockitoAnnotations.initMocks;
import static org.opensrp.common.util.EasyMap.mapOf;
import java.text.Normalizer.Form;
+import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
@@ -21,6 +21,9 @@
import org.opensrp.common.AllConstants.OpenSRPEvent;
import org.opensrp.domain.AppStateToken;
import org.opensrp.dto.form.FormSubmissionDTO;
+import org.opensrp.form.domain.FormData;
+import org.opensrp.form.domain.FormField;
+import org.opensrp.form.domain.FormInstance;
import org.opensrp.form.domain.FormSubmission;
import org.opensrp.form.service.FormSubmissionService;
import org.opensrp.service.ConfigService;
@@ -61,8 +64,8 @@ public void shouldDelegateFormSubmissionToSubmissionService() throws Exception {
@Test
public void shouldFetchFormSubmissionsFromSubmissionService() throws Exception {
- FormSubmission fs1 = new FormSubmission("anm id 1", "instance id 1", "form name", "entity id 1", "1.0", 0L, null),
- fs2 = new FormSubmission("anm id 2", "instance id 2", "form name", "entity id 2", "1.0", 0L, null);
+ FormSubmission fs1 = new FormSubmission("anm id 1", "instance id 1", "form name", "entity id 1", "1.0", 0L, new FormInstance(new FormData("test","def/bindpath", new ArrayList(), null))),
+ fs2 = new FormSubmission("anm id 2", "instance id 2", "form name", "entity id 2", "1.0", 0L, new FormInstance(new FormData("test","def/bindpath", new ArrayList(), null)));
List formSubmissions = asList(fs1,fs2);
when(configService.getAppStateTokenByName(Config.FORM_ENTITY_PARSER_LAST_SYNCED_FORM_SUBMISSION)).thenReturn(new AppStateToken("token", 1L, 0));
when(formSubmissionService.getAllSubmissions(1L, null)).thenReturn(formSubmissions);
diff --git a/opensrp-web/doc/schedules/child-measles.html b/opensrp-web/doc/schedules/child-measles.html
index 548d91f658..999337c624 100644
--- a/opensrp-web/doc/schedules/child-measles.html
+++ b/opensrp-web/doc/schedules/child-measles.html
@@ -6,10 +6,10 @@
-Measles Vaccination