org.akaza.openclinica.control.submit.CreateOneDiscrepancyNoteServlet.java Source code

Java tutorial

Introduction

Here is the source code for org.akaza.openclinica.control.submit.CreateOneDiscrepancyNoteServlet.java

Source

/*
 * OpenClinica is distributed under the
 * GNU Lesser General Public License (GNU LGPL).
 *
 * For details see: http://www.openclinica.org/license
 */
package org.akaza.openclinica.control.submit;

import java.text.MessageFormat;
import java.util.ArrayList;
import java.util.Date;
import java.util.HashMap;
import java.util.Locale;

import javax.servlet.http.HttpSession;

import org.akaza.openclinica.bean.core.DiscrepancyNoteType;
import org.akaza.openclinica.bean.core.NumericComparisonOperator;
import org.akaza.openclinica.bean.core.ResolutionStatus;
import org.akaza.openclinica.bean.core.Status;
import org.akaza.openclinica.bean.core.SubjectEventStatus;
import org.akaza.openclinica.bean.login.UserAccountBean;
import org.akaza.openclinica.bean.managestudy.DiscrepancyNoteBean;
import org.akaza.openclinica.bean.managestudy.StudyBean;
import org.akaza.openclinica.bean.managestudy.StudyEventBean;
import org.akaza.openclinica.bean.managestudy.StudySubjectBean;
import org.akaza.openclinica.bean.submit.EventCRFBean;
import org.akaza.openclinica.bean.submit.ItemBean;
import org.akaza.openclinica.bean.submit.ItemDataBean;
import org.akaza.openclinica.control.core.SecureController;
import org.akaza.openclinica.control.form.FormDiscrepancyNotes;
import org.akaza.openclinica.control.form.FormProcessor;
import org.akaza.openclinica.control.form.Validator;
import org.akaza.openclinica.core.EmailEngine;
import org.akaza.openclinica.dao.login.UserAccountDAO;
import org.akaza.openclinica.dao.managestudy.DiscrepancyNoteDAO;
import org.akaza.openclinica.dao.managestudy.StudyDAO;
import org.akaza.openclinica.dao.managestudy.StudyEventDAO;
import org.akaza.openclinica.dao.managestudy.StudySubjectDAO;
import org.akaza.openclinica.dao.submit.EventCRFDAO;
import org.akaza.openclinica.dao.submit.ItemDAO;
import org.akaza.openclinica.dao.submit.ItemDataDAO;
import org.akaza.openclinica.i18n.core.LocaleResolver;
import org.akaza.openclinica.view.Page;
import org.akaza.openclinica.web.InsufficientPermissionException;
import org.akaza.openclinica.web.SQLInitServlet;
import org.apache.commons.lang.StringUtils;

/**
 * Create a discrepancy note
 *
 */
public class CreateOneDiscrepancyNoteServlet extends SecureController {

    Locale locale;
    // < ResourceBundleresexception,respage;

    //public static final String DIS_TYPES = "discrepancyTypes";
    //public static final String RES_STATUSES = "resolutionStatuses";
    public static final String ENTITY_ID = "id";
    public static final String SUBJECT_ID = "subjectId";
    public static final String ITEM_ID = "itemId";
    public static final String PARENT_ID = "parentId";// parent note id
    public static final String ENTITY_TYPE = "name";
    public static final String ENTITY_COLUMN = "column";
    public static final String ENTITY_FIELD = "field";
    public static final String RES_STATUS_ID = "resStatusId";
    public static final String SUBMITTED_USER_ACCOUNT_ID = "userAccountId";
    public static final String PRESET_USER_ACCOUNT_ID = "preUserAccountId";
    public static final String EMAIL_USER_ACCOUNT = "sendEmail";
    public static final String BOX_DN_MAP = "boxDNMap";
    public static final String BOX_TO_SHOW = "boxToShow";

    /*
     * (non-Javadoc)
     *
     * @see org.akaza.openclinica.control.core.SecureController#mayProceed()
     */
    @Override
    protected void mayProceed() throws InsufficientPermissionException {
        checkStudyLocked(Page.MENU_SERVLET, respage.getString("current_study_locked"));
        locale = LocaleResolver.getLocale(request);

        String exceptionName = resexception.getString("no_permission_to_create_discrepancy_note");
        String noAccessMessage = respage.getString("you_may_not_create_discrepancy_note")
                + respage.getString("change_study_contact_sysadmin");

        if (SubmitDataServlet.mayViewData(ub, currentRole)) {
            return;
        }

        addPageMessage(noAccessMessage);
        throw new InsufficientPermissionException(Page.MENU, exceptionName, "1");
    }

    @Override
    protected void processRequest() throws Exception {
        FormProcessor fp = new FormProcessor(request);
        DiscrepancyNoteDAO dndao = new DiscrepancyNoteDAO(sm.getDataSource());

        int eventCRFId = fp.getInt(CreateDiscrepancyNoteServlet.EVENT_CRF_ID);
        request.setAttribute(CreateDiscrepancyNoteServlet.EVENT_CRF_ID, new Integer(eventCRFId));

        int parentId = fp.getInt(PARENT_ID);
        DiscrepancyNoteBean parent = parentId > 0 ? (DiscrepancyNoteBean) dndao.findByPK(parentId)
                : new DiscrepancyNoteBean();
        HashMap<Integer, DiscrepancyNoteBean> boxDNMap = (HashMap<Integer, DiscrepancyNoteBean>) session
                .getAttribute(BOX_DN_MAP);
        boxDNMap = boxDNMap == null ? new HashMap<Integer, DiscrepancyNoteBean>() : boxDNMap;
        DiscrepancyNoteBean dn = boxDNMap.size() > 0 && boxDNMap.containsKey(Integer.valueOf(parentId))
                ? boxDNMap.get(Integer.valueOf(parentId))
                : new DiscrepancyNoteBean();
        int entityId = fp.getInt(ENTITY_ID, true);
        entityId = entityId > 0 ? entityId : parent.getEntityId();
        if (entityId == 0) {
            Validator.addError(errors, "newChildAdded" + parentId, respage.getString("note_cannot_be_saved"));
            logger.info("entityId is 0. Note saving can not be started.");
        }
        String entityType = fp.getString(ENTITY_TYPE, true);

        FormDiscrepancyNotes noteTree = (FormDiscrepancyNotes) session
                .getAttribute(AddNewSubjectServlet.FORM_DISCREPANCY_NOTES_NAME);
        if (noteTree == null) {
            noteTree = new FormDiscrepancyNotes();
        }
        String ypos = fp.getString("ypos" + parentId);
        int refresh = 0;
        String field = fp.getString(ENTITY_FIELD, true);

        //String description = fp.getString("description" + parentId);
        int typeId = fp.getInt("typeId" + parentId);
        String detailedDes = fp.getString("detailedDes" + parentId);
        int resStatusId = fp.getInt(RES_STATUS_ID + parentId);
        int assignedUserAccountId = fp.getInt(SUBMITTED_USER_ACCOUNT_ID + parentId);
        String viewNoteLink = fp.getString("viewDNLink" + parentId);
        viewNoteLink = this.appendPageFileName(viewNoteLink, "fromBox", "1");

        Validator v = new Validator(request);
        v.addValidation("detailedDes" + parentId, Validator.NO_BLANKS);
        // v.addValidation("description" + parentId, Validator.LENGTH_NUMERIC_COMPARISON, NumericComparisonOperator.LESS_THAN_OR_EQUAL_TO, 255);
        v.addValidation("detailedDes" + parentId, Validator.LENGTH_NUMERIC_COMPARISON,
                NumericComparisonOperator.LESS_THAN_OR_EQUAL_TO, 1000);
        v.addValidation("typeId" + parentId, Validator.NO_BLANKS);
        HashMap errors = v.validate();

        dn.setParentDnId(parentId);
        // dn.setDescription(description);
        dn.setDiscrepancyNoteTypeId(typeId);
        dn.setDetailedNotes(detailedDes);
        dn.setResolutionStatusId(resStatusId);
        if (typeId != DiscrepancyNoteType.ANNOTATION.getId()
                && typeId != DiscrepancyNoteType.REASON_FOR_CHANGE.getId()) {
            dn.setAssignedUserId(assignedUserAccountId);
        }
        if (DiscrepancyNoteType.ANNOTATION.getId() == dn.getDiscrepancyNoteTypeId()) {
            updateStudyEvent(entityType, entityId);
            updateStudySubjectStatus(entityType, entityId);
        }
        if (DiscrepancyNoteType.ANNOTATION.getId() == dn.getDiscrepancyNoteTypeId()
                || DiscrepancyNoteType.REASON_FOR_CHANGE.getId() == dn.getDiscrepancyNoteTypeId()) {
            dn.setResStatus(ResolutionStatus.NOT_APPLICABLE);
            dn.setResolutionStatusId(ResolutionStatus.NOT_APPLICABLE.getId());
        }
        if (DiscrepancyNoteType.FAILEDVAL.getId() == dn.getDiscrepancyNoteTypeId()
                || DiscrepancyNoteType.QUERY.getId() == dn.getDiscrepancyNoteTypeId()) {
            if (ResolutionStatus.NOT_APPLICABLE.getId() == dn.getResolutionStatusId()) {
                Validator.addError(errors, RES_STATUS_ID + parentId, restext.getString("not_valid_res_status"));
            }
        }
        //

        if (errors.isEmpty()) {
            HashMap<String, ArrayList<String>> results = new HashMap<String, ArrayList<String>>();
            ArrayList<String> mess = new ArrayList<String>();

            String column = fp.getString(ENTITY_COLUMN, true);

            dn.setOwner(ub);
            dn.setStudyId(currentStudy.getId());
            dn.setEntityId(entityId);
            dn.setEntityType(entityType);
            dn.setColumn(column);
            dn.setField(field);

            if (parentId > 0) {
                if (dn.getResolutionStatusId() != parent.getResolutionStatusId()) {
                    parent.setResolutionStatusId(dn.getResolutionStatusId());
                    dndao.update(parent);
                    if (!parent.isActive()) {
                        logger.info(
                                "Failed to update resolution status ID for the parent dn ID = " + parentId + ". ");
                    }
                }
                if (dn.getAssignedUserId() != parent.getAssignedUserId()) {
                    parent.setAssignedUserId(dn.getAssignedUserId());
                    if (parent.getAssignedUserId() > 0) {
                        dndao.updateAssignedUser(parent);
                    } else {
                        dndao.updateAssignedUserToNull(parent);
                    }
                    if (!parent.isActive()) {
                        logger.info("Failed to update assigned user ID for the parent dn ID= " + parentId + ". ");
                    }
                }
            } else {
                ypos = "0";
            }

            dn = (DiscrepancyNoteBean) dndao.create(dn);
            boolean success = dn.getId() > 0 ? true : false;
            if (success) {
                refresh = 1;
                dndao.createMapping(dn);
                success = dndao.isQuerySuccessful();
                if (success == false) {
                    mess.add(restext.getString("failed_create_dn_mapping_for_dnId") + dn.getId() + ". ");
                }

                noteTree.addNote(eventCRFId + "_" + field, dn);
                noteTree.addIdNote(dn.getEntityId(), field);
                session.setAttribute(AddNewSubjectServlet.FORM_DISCREPANCY_NOTES_NAME, noteTree);
                if (dn.getParentDnId() == 0) {
                    // see issue 2659 this is a new thread, we will create
                    // two notes in this case,
                    // This way one can be the parent that updates as the
                    // status changes, but one also stays as New.
                    dn.setParentDnId(dn.getId());
                    dn = (DiscrepancyNoteBean) dndao.create(dn);
                    if (dn.getId() > 0) {
                        dndao.createMapping(dn);
                        if (!dndao.isQuerySuccessful()) {
                            mess.add(restext.getString("failed_create_dn_mapping_for_dnId") + dn.getId() + ". ");
                        }
                        noteTree.addNote(eventCRFId + "_" + field, dn);
                        noteTree.addIdNote(dn.getEntityId(), field);
                        session.setAttribute(AddNewSubjectServlet.FORM_DISCREPANCY_NOTES_NAME, noteTree);
                    } else {
                        mess.add(restext.getString("failed_create_child_dn_for_new_parent_dnId") + dn.getId()
                                + ". ");
                    }
                }
            } else {
                mess.add(restext.getString("failed_create_new_dn") + ". ");
            }

            if (success) {
                if (boxDNMap.size() > 0 && boxDNMap.containsKey(parentId)) {
                    boxDNMap.remove(parentId);
                }
                session.removeAttribute(BOX_TO_SHOW);
                /*
                 * Copied from CreateDiscrepancyNoteServlet
                 * Setting a marker to check
                 * later while saving administrative edited data. This is needed to
                 * make sure the system flags error while changing data for items
                 * which already has a DiscrepanyNote
                 */
                manageReasonForChangeState(session, eventCRFId + "_" + field);
                String email = fp.getString(EMAIL_USER_ACCOUNT + parentId);
                if (dn.getAssignedUserId() > 0 && "1".equals(email.trim())) {
                    logger.info("++++++ found our way here");
                    // generate email for user here
                    StringBuffer message = new StringBuffer();

                    dn = getNoteInfo(dn);

                    // generate message here
                    EmailEngine em = new EmailEngine(EmailEngine.getSMTPHost());
                    UserAccountDAO userAccountDAO = new UserAccountDAO(sm.getDataSource());
                    ItemDAO itemDAO = new ItemDAO(sm.getDataSource());
                    ItemDataDAO iddao = new ItemDataDAO(sm.getDataSource());
                    ItemBean item = new ItemBean();
                    ItemDataBean itemData = new ItemDataBean();

                    StudyDAO studyDAO = new StudyDAO(sm.getDataSource());
                    UserAccountBean assignedUser = (UserAccountBean) userAccountDAO
                            .findByPK(dn.getAssignedUserId());
                    String alertEmail = assignedUser.getEmail();
                    message.append(MessageFormat.format(respage.getString("mailDNHeader"),
                            assignedUser.getFirstName(), assignedUser.getLastName()));
                    message.append("<A HREF='" + SQLInitServlet.getField("sysURL.base")
                            + "ViewNotes?module=submit&listNotes_f_discrepancyNoteBean.user="
                            + assignedUser.getName() + "&listNotes_f_entityName=" + dn.getEntityName() + "'>"
                            + SQLInitServlet.getField("sysURL.base") + "</A><BR/>");
                    message.append(respage.getString("you_received_this_from"));
                    StudyBean study = (StudyBean) studyDAO.findByPK(dn.getStudyId());

                    if ("itemData".equalsIgnoreCase(entityType)) {
                        itemData = (ItemDataBean) iddao.findByPK(dn.getEntityId());
                        item = (ItemBean) itemDAO.findByPK(itemData.getItemId());
                    }

                    message.append(respage.getString("email_body_separator"));
                    message.append(respage.getString("disc_note_info"));
                    message.append(respage.getString("email_body_separator"));
                    message.append(MessageFormat.format(respage.getString("mailDNParameters1"),
                            dn.getDetailedNotes(), ub.getName()));
                    message.append(respage.getString("email_body_separator"));
                    message.append(respage.getString("entity_information"));
                    message.append(respage.getString("email_body_separator"));
                    message.append(MessageFormat.format(respage.getString("mailDNParameters2"), study.getName(),
                            dn.getSubjectName()));

                    if (!("studySub".equalsIgnoreCase(entityType) || "subject".equalsIgnoreCase(entityType))) {
                        message.append(
                                MessageFormat.format(respage.getString("mailDNParameters3"), dn.getEventName()));
                        if (!"studyEvent".equalsIgnoreCase(dn.getEntityType())) {
                            message.append(
                                    MessageFormat.format(respage.getString("mailDNParameters4"), dn.getCrfName()));
                            if (!"eventCrf".equalsIgnoreCase(dn.getEntityType())) {
                                message.append(MessageFormat.format(respage.getString("mailDNParameters6"),
                                        item.getName()));
                            }
                        }
                    }

                    message.append(respage.getString("email_body_separator"));
                    message.append(MessageFormat.format(respage.getString("mailDNThanks"), study.getName()));
                    message.append(respage.getString("email_body_separator"));
                    message.append(respage.getString("disclaimer"));
                    message.append(respage.getString("email_body_separator"));
                    message.append(respage.getString("email_footer"));

                    /*
                     *
                     *
                     *
                     * Please select the link below to view the information
                     * provided. You may need to login to
                     * OpenClinica_testbed with your user name and password
                     * after selecting the link. If you receive a page
                     * cannot be displayed message, please make sure to
                     * select the Change Study/Site link in the upper right
                     * table of the page, select the study referenced above,
                     * and select the link again.
                     *
                     * https://openclinica.s-3.com/OpenClinica_testbed/
                     * ViewSectionDataEntry ?ecId=117&sectionId=142&tabId=2
                     */

                    String emailBodyString = message.toString();
                    String entityName = getEntityName(dn);
                    sendEmail(alertEmail.trim(), EmailEngine.getAdminEmail(),
                            MessageFormat.format(respage.getString("mailDNSubject"), study.getName(), entityName),
                            emailBodyString, true, null, null, true);
                }

                String close = fp.getString("close" + parentId);
                //session.setAttribute(CLOSE_WINDOW, "true".equals(close)?"true":"");
                if ("true".equals(close)) {
                    addPageMessage(respage.getString("note_saved_into_db"));
                    addPageMessage(respage.getString("page_close_automatically"));
                    forwardPage(Page.ADD_DISCREPANCY_NOTE_SAVE_DONE);
                    logger.info("Should forwardPage to ADD_DISCREPANCY_NOTE_SAVE_DONE.");
                } else {
                    if (parentId == dn.getParentDnId()) {
                        mess.add(restext.getString("a_new_child_dn_added"));
                        results.put("newChildAdded" + parentId, mess);
                        setInputMessages(results);
                    } else {
                        addPageMessage(restext.getString("a_new_dn_thread_added"));
                    }
                }

            } else {
                session.setAttribute(BOX_TO_SHOW, parentId + "");
            }

        } else {
            setInputMessages(errors);
            boxDNMap.put(Integer.valueOf(parentId), dn);
            session.setAttribute(BOX_TO_SHOW, parentId + "");
        }
        session.setAttribute(BOX_DN_MAP, boxDNMap);
        viewNoteLink = this.appendPageFileName(viewNoteLink, "refresh", refresh + "");
        viewNoteLink = this.appendPageFileName(viewNoteLink, "y", ypos != null && ypos.length() > 0 ? ypos : "0");

        getServletContext().getRequestDispatcher(viewNoteLink).forward(request, response);
        // forwardPage(Page.setNewPage(viewNoteLink, Page.VIEW_DISCREPANCY_NOTE.getTitle()));

    }

    private void updateStudySubjectStatus(String entityType, int entityId) {
        if ("itemData".equalsIgnoreCase(entityType)) {
            int itemDataId = entityId;
            ItemDataDAO iddao = new ItemDataDAO(sm.getDataSource());
            ItemDataBean itemData = (ItemDataBean) iddao.findByPK(itemDataId);
            EventCRFDAO ecdao = new EventCRFDAO(sm.getDataSource());
            StudyEventDAO svdao = new StudyEventDAO(sm.getDataSource());
            StudySubjectDAO studySubjectDAO = new StudySubjectDAO(sm.getDataSource());
            EventCRFBean ec = (EventCRFBean) ecdao.findByPK(itemData.getEventCRFId());
            StudyEventBean event = (StudyEventBean) svdao.findByPK(ec.getStudyEventId());
            StudySubjectBean studySubject = (StudySubjectBean) studySubjectDAO.findByPK(event.getStudySubjectId());
            if (studySubject.getStatus() != null && studySubject.getStatus().equals(Status.SIGNED)) {
                studySubject.setStatus(Status.AVAILABLE);
                studySubject.setUpdater(ub);
                studySubject.setUpdatedDate(new Date());
                studySubjectDAO.update(studySubject);
            }
            if (ec.isSdvStatus()) {
                studySubject.setStatus(Status.AVAILABLE);
                studySubject.setUpdater(ub);
                studySubject.setUpdatedDate(new Date());
                studySubjectDAO.update(studySubject);
                ec.setSdvStatus(false);
                ecdao.update(ec);
            }

        }
    }

    private void updateStudyEvent(String entityType, int entityId) {
        if ("itemData".equalsIgnoreCase(entityType)) {
            int itemDataId = entityId;
            ItemDataDAO iddao = new ItemDataDAO(sm.getDataSource());
            ItemDataBean itemData = (ItemDataBean) iddao.findByPK(itemDataId);
            EventCRFDAO ecdao = new EventCRFDAO(sm.getDataSource());
            StudyEventDAO svdao = new StudyEventDAO(sm.getDataSource());
            EventCRFBean ec = (EventCRFBean) ecdao.findByPK(itemData.getEventCRFId());
            StudyEventBean event = (StudyEventBean) svdao.findByPK(ec.getStudyEventId());
            if (event.getSubjectEventStatus().equals(SubjectEventStatus.SIGNED)) {
                event.setSubjectEventStatus(SubjectEventStatus.COMPLETED);
                event.setUpdater(ub);
                event.setUpdatedDate(new Date());
                svdao.update(event);
            }
        } else if ("eventCrf".equalsIgnoreCase(entityType)) {
            int eventCRFId = entityId;
            EventCRFDAO ecdao = new EventCRFDAO(sm.getDataSource());
            StudyEventDAO svdao = new StudyEventDAO(sm.getDataSource());

            EventCRFBean ec = (EventCRFBean) ecdao.findByPK(eventCRFId);
            StudyEventBean event = (StudyEventBean) svdao.findByPK(ec.getStudyEventId());
            if (event.getSubjectEventStatus().equals(SubjectEventStatus.SIGNED)) {
                event.setSubjectEventStatus(SubjectEventStatus.COMPLETED);
                event.setUpdater(ub);
                event.setUpdatedDate(new Date());
                svdao.update(event);
            }
        }
    }

    private void manageReasonForChangeState(HttpSession session, String itemDataBeanId) {
        HashMap<String, Boolean> noteSubmitted = (HashMap<String, Boolean>) session
                .getAttribute(DataEntryServlet.NOTE_SUBMITTED);
        if (noteSubmitted == null) {
            noteSubmitted = new HashMap<String, Boolean>();
        }
        noteSubmitted.put(itemDataBeanId, Boolean.TRUE);
        session.setAttribute(DataEntryServlet.NOTE_SUBMITTED, noteSubmitted);
    }

    private String appendPageFileName(String origin, String parameterName, String parameterValue) {
        String parameter = parameterName + "=" + parameterValue;
        String[] a = origin.split("\\?");
        if (a.length == 2) {
            if (("&" + a[1]).contains("&" + parameterName + "=")) {
                String result = a[0] + "?";
                String[] b = ("&" + a[1]).split("&" + parameterName + "=");
                if (b.length == 2) {
                    result += b[0].substring(1) + "&" + parameter
                            + (b[1].contains("&") ? b[1].substring(b[1].indexOf("&")) : "");
                    return result;
                } else if (b.length > 2) {
                    result += b[0].substring(1) + "&" + parameter;
                    for (int i = 2; i < b.length - 2; ++i) {
                        result += b[i].substring(b[i].indexOf("&"));
                    }
                    int j = b.length - 1;
                    result += b[j].contains("&") ? b[j].substring(b[j].indexOf("&")) : "";
                    return result;
                }

            } else {
                return origin + "&" + parameter;
            }
        } else if (a.length == 1) {
            if (origin.endsWith("?")) {
                return origin + parameter;
            } else {
                return origin + "?" + parameter;
            }
        }
        logger.info("Original pageFileName: " + origin);
        return origin;
    }

    private String getEntityName(DiscrepancyNoteBean note) {
        String entityType = "";
        if (!StringUtils.isBlank(note.getEntityType())) {
            if ("itemData".equalsIgnoreCase(note.getEntityType())) {
                return note.getEntityName();
            }
            if (!StringUtils.isBlank(note.getColumn())) {
                if ("studySub".equalsIgnoreCase(note.getEntityType())) {
                    if ("enrollment_date".equalsIgnoreCase(note.getColumn())) {
                        entityType = resword.getString("enrollment_date");
                    }
                } else if ("subject".equalsIgnoreCase(note.getEntityType())) {
                    if ("gender".equalsIgnoreCase(note.getColumn())) {
                        entityType = resword.getString("gender");
                    } else if ("date_of_birth".equalsIgnoreCase(note.getColumn())) {
                        entityType = resword.getString("date_of_birth");
                    } else if ("unique_identifier".equalsIgnoreCase(note.getColumn())) {
                        entityType = resword.getString("unique_identifier");
                    }
                } else if ("studyEvent".equalsIgnoreCase(note.getEntityType())) {
                    if ("start_date".equalsIgnoreCase(note.getColumn())) {
                        entityType = resword.getString("start_date");
                    } else if ("end_date".equalsIgnoreCase(note.getColumn())) {
                        entityType = resword.getString("end_date");
                    }
                }
            }
        }
        return entityType;

    }
}