/**
* This Source Code Form is subject to the terms of the Mozilla Public License,
* v. 2.0. If a copy of the MPL was not distributed with this file, You can
* obtain one at http://mozilla.org/MPL/2.0/. OpenMRS is also distributed under
* the terms of the Healthcare Disclaimer located at http://openmrs.org/license.
*
* Copyright (C) OpenMRS Inc. OpenMRS is a registered trademark and the OpenMRS
* graphic logo is a trademark of OpenMRS Inc.
*/
package org.openmrs.api.impl;
import java.util.Collection;
import java.util.Collections;
import java.util.Date;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.UUID;
import org.hibernate.exception.ConstraintViolationException;
import org.openmrs.Concept;
import org.openmrs.ConceptComplex;
import org.openmrs.EncounterType;
import org.openmrs.Field;
import org.openmrs.FieldAnswer;
import org.openmrs.FieldType;
import org.openmrs.Form;
import org.openmrs.FormField;
import org.openmrs.FormResource;
import org.openmrs.aop.RequiredDataAdvice;
import org.openmrs.api.APIException;
import org.openmrs.api.FormService;
import org.openmrs.api.FormsLockedException;
import org.openmrs.api.InvalidFileTypeException;
import org.openmrs.api.context.Context;
import org.openmrs.api.db.FormDAO;
import org.openmrs.api.handler.SaveHandler;
import org.openmrs.customdatatype.CustomDatatypeUtil;
import org.openmrs.obs.ComplexObsHandler;
import org.openmrs.obs.SerializableComplexObsHandler;
import org.openmrs.util.OpenmrsConstants;
import org.openmrs.util.OpenmrsUtil;
import org.openmrs.validator.FormValidator;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.validation.BindException;
/**
* Default implementation of the {@link FormService}
* <p>
* This class should not be instantiated alone, get a service class from the Context:
* Context.getFormService();
*
* @see org.openmrs.api.context.Context
* @see org.openmrs.api.FormService
*/
@Transactional
public class FormServiceImpl extends BaseOpenmrsService implements FormService {
protected final Logger log = LoggerFactory.getLogger(getClass());
private FormDAO dao;
private final FormValidator formValidator;
/**
* Default empty constructor
*/
public FormServiceImpl() {
formValidator = new FormValidator();
}
/**
* Method used to inject the data access object.
*
* @param dao
*/
public void setFormDAO(FormDAO dao) {
this.dao = dao;
}
/**
* @see org.openmrs.api.FormService#getForm(java.lang.Integer)
*/
@Override
@Transactional(readOnly = true)
public Form getForm(Integer formId) throws APIException {
return dao.getForm(formId);
}
/**
* Duplicate this form and form_fields associated with this form
*
* @param form
* @return New duplicated form
* @throws APIException
* @see org.openmrs.api.FormService#duplicateForm(org.openmrs.Form)
*/
@Override
public Form duplicateForm(Form form) throws APIException {
// Map of /Old FormFieldId/ to /New FormField Object/
//TreeMap<Integer, FormField> formFieldMap = new TreeMap<Integer, FormField>();
//formFieldMap.put(null, null); //for parentless formFields
checkIfFormsAreLocked();
// get original form id for reference later
Integer originalFormId = form.getFormId();
for (FormField formField : form.getFormFields()) {
//formFieldMap.put(formField.getFormFieldId(), formField);
formField.setUuid(null);
formField.setFormFieldId(null);
//formField.setParent(formFieldMap.get(formField.getParent().getFormFieldId()));
}
// this is required because Hibernate would recognize the original collection
form.setFormFields(new HashSet<FormField>(form.getFormFields()));
form.setUuid(null);
form.setFormId(null);
form.setCreator(null);
form.setDateCreated(null);
form.setChangedBy(null);
form.setDateChanged(null);
Context.clearSession();
RequiredDataAdvice.recursivelyHandle(SaveHandler.class, form, null);
Form newForm = dao.duplicateForm(form);
// duplicate form resources from the old form to the new one
duplicateFormResources(Context.getFormService().getForm(originalFormId), newForm);
return newForm;
}
/**
* @see org.openmrs.api.FormService#retireForm(org.openmrs.Form, java.lang.String)
*/
@Override
public void retireForm(Form form, String reason) throws APIException {
form.setRetired(true);
form.setRetireReason(reason);
Context.getFormService().saveForm(form);
}
/**
* @see org.openmrs.api.FormService#unretireForm(org.openmrs.Form)
*/
@Override
public void unretireForm(Form form) throws APIException {
form.setRetired(false);
Context.getFormService().saveForm(form);
}
/**
* @see org.openmrs.api.FormService#getAllFieldTypes()
*/
@Override
@Transactional(readOnly = true)
public List<FieldType> getAllFieldTypes() throws APIException {
return Context.getFormService().getAllFieldTypes(true);
}
/**
* @see org.openmrs.api.FormService#getAllFieldTypes(boolean)
*/
@Override
@Transactional(readOnly = true)
public List<FieldType> getAllFieldTypes(boolean includeRetired) throws APIException {
return dao.getAllFieldTypes(includeRetired);
}
/**
* @see org.openmrs.api.FormService#getFieldType(java.lang.Integer)
*/
@Override
@Transactional(readOnly = true)
public FieldType getFieldType(Integer fieldTypeId) throws APIException {
return dao.getFieldType(fieldTypeId);
}
/**
* @see org.openmrs.api.FormService#getField(java.lang.Integer)
*/
@Override
@Transactional(readOnly = true)
public Field getField(Integer fieldId) throws APIException {
return dao.getField(fieldId);
}
/**
* @see org.openmrs.api.FormService#getFormField(java.lang.Integer)
*/
@Override
@Transactional(readOnly = true)
public FormField getFormField(Integer formFieldId) throws APIException {
return dao.getFormField(formFieldId);
}
/**
* @see org.openmrs.api.FormService#getFormField(org.openmrs.Form, org.openmrs.Concept,
* java.util.Collection, boolean)
*/
@Override
@Transactional(readOnly = true)
public FormField getFormField(Form form, Concept concept, Collection<FormField> ignoreFormFields, boolean force)
throws APIException {
// create an empty ignoreFormFields list if none was passed in
if (ignoreFormFields == null) {
ignoreFormFields = Collections.emptyList();
}
return dao.getFormField(form, concept, ignoreFormFields, force);
}
/**
* @see org.openmrs.api.FormService#getFieldByUuid(java.lang.String)
*/
@Override
@Transactional(readOnly = true)
public Field getFieldByUuid(String uuid) throws APIException {
return dao.getFieldByUuid(uuid);
}
@Override
@Transactional(readOnly = true)
public FieldAnswer getFieldAnswerByUuid(String uuid) throws APIException {
return dao.getFieldAnswerByUuid(uuid);
}
/**
* @see org.openmrs.api.FormService#getFieldTypeByUuid(java.lang.String)
*/
@Override
@Transactional(readOnly = true)
public FieldType getFieldTypeByUuid(String uuid) throws APIException {
return dao.getFieldTypeByUuid(uuid);
}
/**
* @see org.openmrs.api.FormService#getFieldTypeByName(java.lang.String)
*/
@Override
@Transactional(readOnly = true)
public FieldType getFieldTypeByName(String name) throws APIException {
return dao.getFieldTypeByName(name);
}
/**
* @see org.openmrs.api.FormService#getFormByUuid(java.lang.String)
*/
@Override
@Transactional(readOnly = true)
public Form getFormByUuid(String uuid) throws APIException {
return dao.getFormByUuid(uuid);
}
/**
* @see org.openmrs.api.FormService#getFormFieldByUuid(java.lang.String)
*/
@Override
@Transactional(readOnly = true)
public FormField getFormFieldByUuid(String uuid) throws APIException {
return dao.getFormFieldByUuid(uuid);
}
/**
* @see org.openmrs.api.FormService#getAllFields()
*/
@Override
@Transactional(readOnly = true)
public List<Field> getAllFields() throws APIException {
return Context.getFormService().getAllFields(true);
}
/**
* @see org.openmrs.api.FormService#getAllFields(boolean)
*/
@Override
@Transactional(readOnly = true)
public List<Field> getAllFields(boolean includeRetired) throws APIException {
return dao.getAllFields(includeRetired);
}
/**
* @see org.openmrs.api.FormService#getAllFormFields()
*/
@Override
@Transactional(readOnly = true)
public List<FormField> getAllFormFields() throws APIException {
return dao.getAllFormFields();
}
/**
* @see org.openmrs.api.FormService#getAllForms()
*/
@Override
@Transactional(readOnly = true)
public List<Form> getAllForms() throws APIException {
return Context.getFormService().getAllForms(true);
}
/**
* @see org.openmrs.api.FormService#getAllForms(boolean)
*/
@Override
@Transactional(readOnly = true)
public List<Form> getAllForms(boolean includeRetired) throws APIException {
return dao.getAllForms(includeRetired);
}
/**
* @see org.openmrs.api.FormService#getFields(java.util.Collection, java.util.Collection,
* java.util.Collection, java.util.Collection, java.util.Collection, java.lang.Boolean,
* java.util.Collection, java.util.Collection, java.lang.Boolean)
*/
@Override
@Transactional(readOnly = true)
public List<Field> getFields(Collection<Form> forms, Collection<FieldType> fieldTypes, Collection<Concept> concepts,
Collection<String> tableNames, Collection<String> attributeNames, Boolean selectMultiple,
Collection<FieldAnswer> containsAllAnswers, Collection<FieldAnswer> containsAnyAnswer, Boolean retired)
throws APIException {
if (forms == null) {
forms = Collections.emptyList();
}
if (fieldTypes == null) {
fieldTypes = Collections.emptyList();
}
if (concepts == null) {
concepts = Collections.emptyList();
}
if (tableNames == null) {
tableNames = Collections.emptyList();
}
if (attributeNames == null) {
attributeNames = Collections.emptyList();
}
if (containsAllAnswers == null) {
containsAllAnswers = Collections.emptyList();
}
if (containsAnyAnswer == null) {
containsAnyAnswer = Collections.emptyList();
}
return dao.getFields(forms, fieldTypes, concepts, tableNames, attributeNames, selectMultiple, containsAllAnswers,
containsAnyAnswer, retired);
}
/**
* @see org.openmrs.api.FormService#getForm(java.lang.String)
* @should return the form with the highest version, if more than one form with the given name
* exists
*/
@Override
@Transactional(readOnly = true)
public Form getForm(String name) throws APIException {
List<Form> forms = dao.getFormsByName(name);
if (forms == null || forms.isEmpty()) {
return null;
} else {
return forms.get(0);
}
}
/**
* @see org.openmrs.api.FormService#getForm(java.lang.String, java.lang.String)
*/
@Override
@Transactional(readOnly = true)
public Form getForm(String name, String version) throws APIException {
return dao.getForm(name, version);
}
/**
* @see org.openmrs.api.FormService#getForms(java.lang.String, boolean)
*/
@Override
@Transactional(readOnly = true)
public List<Form> getForms(String fuzzyName, boolean onlyLatestVersion) {
// get all forms including unpublished and including retired
List<Form> forms = Context.getFormService().getForms(fuzzyName, null, null, null, null, null, null);
Set<String> namesAlreadySeen = new HashSet<String>();
for (Iterator<Form> i = forms.iterator(); i.hasNext();) {
Form form = i.next();
if (namesAlreadySeen.contains(form.getName())) {
i.remove();
} else {
namesAlreadySeen.add(form.getName());
}
}
return forms;
}
/**
* @see org.openmrs.api.FormService#getForms(java.lang.String, java.lang.Boolean,
* java.util.Collection, java.lang.Boolean, java.util.Collection, java.util.Collection,
* java.util.Collection)
*/
@Override
@Transactional(readOnly = true)
public List<Form> getForms(String partialName, Boolean published, Collection<EncounterType> encounterTypes,
Boolean retired, Collection<FormField> containingAnyFormField, Collection<FormField> containingAllFormFields,
Collection<Field> fields) {
if (encounterTypes == null) {
encounterTypes = Collections.emptyList();
}
if (containingAllFormFields == null) {
containingAllFormFields = Collections.emptyList();
}
if (containingAnyFormField == null) {
containingAnyFormField = Collections.emptyList();
}
if (fields == null) {
fields = Collections.emptyList();
}
return dao.getForms(partialName, published, encounterTypes, retired, containingAnyFormField,
containingAllFormFields, fields);
}
/**
* @see org.openmrs.api.FormService#getFormCount(java.lang.String, java.lang.Boolean,
* java.util.Collection, java.lang.Boolean, java.util.Collection, java.util.Collection,
* java.util.Collection)
*/
@Override
@Transactional(readOnly = true)
public Integer getFormCount(String partialName, Boolean published, Collection<EncounterType> encounterTypes,
Boolean retired, Collection<FormField> containingAnyFormField, Collection<FormField> containingAllFormFields,
Collection<Field> fields) {
if (encounterTypes == null) {
encounterTypes = Collections.emptyList();
}
if (containingAllFormFields == null) {
containingAllFormFields = Collections.emptyList();
}
if (containingAnyFormField == null) {
containingAnyFormField = Collections.emptyList();
}
if (fields == null) {
fields = Collections.emptyList();
}
return dao.getFormCount(partialName, published, encounterTypes, retired, containingAnyFormField,
containingAllFormFields, fields);
}
/**
* @see org.openmrs.api.FormService#getPublishedForms()
*/
@Override
@Transactional(readOnly = true)
public List<Form> getPublishedForms() throws APIException {
return Context.getFormService().getForms(null, true, null, false, null, null, null);
}
/**
* @see org.openmrs.api.FormService#purgeField(org.openmrs.Field)
*/
@Override
public void purgeField(Field field) throws APIException {
Context.getFormService().purgeField(field, false);
}
/**
* @see org.openmrs.api.FormService#purgeField(org.openmrs.Field, boolean)
*/
@Override
public void purgeField(Field field, boolean cascade) throws APIException {
if (cascade) {
throw new APIException("general.not.yet.implemented", (Object[]) null);
} else {
dao.deleteField(field);
}
}
/**
* @see org.openmrs.api.FormService#purgeForm(org.openmrs.Form)
*/
@Override
public void purgeForm(Form form) throws APIException {
checkIfFormsAreLocked();
Context.getFormService().purgeForm(form, false);
}
/**
* @see org.openmrs.api.FormService#purgeForm(org.openmrs.Form, boolean)
*/
@Override
public void purgeForm(Form form, boolean cascade) throws APIException {
if (cascade) {
throw new APIException("general.not.yet.implemented", (Object[]) null);
}
// remove resources
for (FormResource resource : Context.getFormService().getFormResourcesForForm(form)) {
Context.getFormService().purgeFormResource(resource);
}
dao.deleteForm(form);
}
/**
* @see org.openmrs.api.FormService#purgeFormField(org.openmrs.FormField)
*/
@Override
public void purgeFormField(FormField formField) throws APIException {
dao.deleteFormField(formField);
}
/**
* @see org.openmrs.api.FormService#retireField(org.openmrs.Field)
*/
@Override
public Field retireField(Field field) throws APIException {
if (!field.getRetired()) {
field.setRetired(true);
return Context.getFormService().saveField(field);
} else {
return field;
}
}
/**
* @see org.openmrs.api.FormService#saveField(org.openmrs.Field)
*/
@Override
public Field saveField(Field field) throws APIException {
return dao.saveField(field);
}
/**
* @see org.openmrs.api.FormService#saveForm(org.openmrs.Form)
*/
@Override
public Form saveForm(Form form) throws APIException {
checkIfFormsAreLocked();
BindException errors = new BindException(form, "form");
formValidator.validate(form, errors);
if (errors.hasErrors()) {
throw new APIException(errors);
}
if (form.getFormFields() != null) {
for (FormField ff : form.getFormFields()) {
if (ff.getForm() == null) {
ff.setForm(form);
} else if (!ff.getForm().equals(form)) {
throw new APIException("Form.contains.FormField.error", new Object[] { ff });
}
}
}
return dao.saveForm(form);
}
/**
* @see org.openmrs.api.FormService#saveFormField(org.openmrs.FormField)
*/
@Override
public FormField saveFormField(FormField formField) throws APIException {
Field field = formField.getField();
if (field.getCreator() == null) {
field.setCreator(Context.getAuthenticatedUser());
}
if (field.getDateCreated() == null) {
field.setDateCreated(new Date());
}
// don't change the changed by and date changed on field for
// form field updates
// set the uuid here because the RequiredDataAdvice only looks at child lists
if (field.getUuid() == null) {
field.setUuid(UUID.randomUUID().toString());
}
formField = dao.saveFormField(formField);
//Include all formfields from all serializable complex obs handlers
Concept concept = formField.getField().getConcept();
if (concept != null && concept.isComplex()) {
ComplexObsHandler handler = Context.getObsService().getHandler(((ConceptComplex) concept).getHandler());
if (handler instanceof SerializableComplexObsHandler) {
SerializableComplexObsHandler sHandler = (SerializableComplexObsHandler) handler;
if (sHandler.getFormFields() != null) {
for (FormField ff : sHandler.getFormFields()) {
ff.setParent(formField);
ff.setForm(formField.getForm());
ff.setCreator(formField.getCreator());
ff.setDateCreated(formField.getDateCreated());
dao.saveFormField(ff);
}
}
}
}
return formField;
}
/**
* @see org.openmrs.api.FormService#unretireField(org.openmrs.Field)
*/
@Override
public Field unretireField(Field field) throws APIException {
if (field.getRetired()) {
field.setRetired(false);
return Context.getFormService().saveField(field);
} else {
return field;
}
}
/**
* @see org.openmrs.api.FormService#getFields(java.lang.String)
*/
@Override
public List<Field> getFields(String fuzzySearchPhrase) throws APIException {
return dao.getFields(fuzzySearchPhrase);
}
/**
* @see org.openmrs.api.FormService#getFieldsByConcept(org.openmrs.Concept)
*/
@Override
@Transactional(readOnly = true)
public List<Field> getFieldsByConcept(Concept concept) throws APIException {
return Context.getFormService().getFields(null, null, Collections.singleton(concept), null, null, null, null, null,
null);
}
/**
* @see org.openmrs.api.FormService#getFormsContainingConcept(org.openmrs.Concept)
*/
@Override
@Transactional(readOnly = true)
public List<Form> getFormsContainingConcept(Concept concept) throws APIException {
if (concept.getConceptId() == null) {
return Collections.emptyList();
}
return dao.getFormsContainingConcept(concept);
}
/**
* @see org.openmrs.api.FormService#purgeFieldType(org.openmrs.FieldType)
*/
@Override
public void purgeFieldType(FieldType fieldType) throws APIException {
dao.deleteFieldType(fieldType);
}
/**
* @see org.openmrs.api.FormService#saveFieldType(org.openmrs.FieldType)
*/
@Override
public FieldType saveFieldType(FieldType fieldType) throws APIException {
return dao.saveFieldType(fieldType);
}
/**
* @see FormService#mergeDuplicateFields()
*/
@Override
public int mergeDuplicateFields() throws APIException {
List<Field> fields = dao.getAllFields(true);
Set<Field> fieldsToDelete = new HashSet<Field>();
Map<String, Integer> fieldNameAsKeyAndFieldIdAsValueMap = new HashMap<String, Integer>();
for (Field field : fields) {
if (fieldNameAsKeyAndFieldIdAsValueMap.containsKey(field.getName())) {
Field fieldToCompareTo = dao.getField(fieldNameAsKeyAndFieldIdAsValueMap.get(field.getName()));
if (fieldsAreSimilar(field, fieldToCompareTo)) {
//get the formFields that use this duplicate field
List<FormField> formFields = dao.getFormFieldsByField(field);
//for each of the formFields that use this duplicate field
//replace with field from outer loop
for (FormField formField : formFields) {
formField.setField(fieldToCompareTo);
dao.saveFormField(formField);
fieldsToDelete.add(field);
}
} else {
fieldNameAsKeyAndFieldIdAsValueMap.put(field.getName(), field.getId());
}
} else {
fieldNameAsKeyAndFieldIdAsValueMap.put(field.getName(), field.getId());
}
}
for (Field field : fieldsToDelete) {
dao.deleteField(field);
}
return fieldsToDelete.size();
}
private boolean fieldsAreSimilar(Field field, Field fieldToBeReplaced) {
return (OpenmrsUtil.nullSafeEquals(field.getName(), fieldToBeReplaced.getName())
&& OpenmrsUtil.nullSafeEquals(field.getSelectMultiple(), fieldToBeReplaced.getSelectMultiple())
&& OpenmrsUtil.nullSafeEquals(field.getFieldType(), fieldToBeReplaced.getFieldType())
&& OpenmrsUtil.nullSafeEquals(field.getConcept(), fieldToBeReplaced.getConcept())
&& OpenmrsUtil.nullSafeEquals(field.getTableName(), fieldToBeReplaced.getTableName())
&& OpenmrsUtil.nullSafeEquals(field.getDefaultValue(), fieldToBeReplaced.getDefaultValue())
&& field.getRetired() != null && !field.getRetired());
}
/**
* @see org.openmrs.api.FormService#getFormResource(java.lang.Integer)
*/
@Override
@Transactional(readOnly = true)
public FormResource getFormResource(Integer formResourceId) throws APIException {
return dao.getFormResource(formResourceId);
}
/**
* @see org.openmrs.api.FormService#getFormResourceByUuid(java.lang.String)
*/
@Override
@Transactional(readOnly = true)
public FormResource getFormResourceByUuid(String uuid) throws APIException {
return dao.getFormResourceByUuid(uuid);
}
/**
* @see org.openmrs.api.FormService#getFormResource(org.openmrs.Form, java.lang.String)
*/
@Override
@Transactional(readOnly = true)
public FormResource getFormResource(Form form, String name) throws APIException {
return dao.getFormResource(form, name);
}
/**
* @see org.openmrs.api.FormService#saveFormResource(org.openmrs.FormResource)
*/
@Override
public FormResource saveFormResource(FormResource formResource) throws APIException {
if (formResource == null) {
return null;
}
// If a form resource with same name exists, replace it with current value
FormResource toPersist = formResource;
FormResource original = Context.getFormService().getFormResource(formResource.getForm(), formResource.getName());
if (original != null) {
original.setName(formResource.getName());
original.setValue(formResource.getValue());
original.setDatatypeClassname(formResource.getDatatypeClassname());
original.setDatatypeConfig(formResource.getDatatypeConfig());
original.setPreferredHandlerClassname(formResource.getPreferredHandlerClassname());
toPersist = original;
}
try {
CustomDatatypeUtil.saveIfDirty(toPersist);
}
catch (ConstraintViolationException ex) {
throw new InvalidFileTypeException(ex.getMessage(), ex);
}
return dao.saveFormResource(toPersist);
}
/**
* @see org.openmrs.api.FormService#purgeFormResource(org.openmrs.FormResource)
*/
@Override
public void purgeFormResource(FormResource formResource) throws APIException {
dao.deleteFormResource(formResource);
}
/**
* @see org.openmrs.api.FormService#getFormResourcesForForm(org.openmrs.Form)
*/
@Override
@Transactional(readOnly = true)
public Collection<FormResource> getFormResourcesForForm(Form form) throws APIException {
return dao.getFormResourcesForForm(form);
}
/**
* duplicates form resources from one form to another
*
* @param source the form to copy resources from
* @param destination the form to copy resources to
*/
private void duplicateFormResources(Form source, Form destination) {
FormService service = Context.getFormService();
for (FormResource resource : service.getFormResourcesForForm(source)) {
FormResource newResource = new FormResource(resource);
newResource.setForm(destination);
service.saveFormResource(newResource);
}
}
/*
* @see org.openmrs.api.FormService#checkIfFormsAreLocked()
* @see FormsLockedException
*/
@Override
public void checkIfFormsAreLocked() {
String locked = Context.getAdministrationService().getGlobalProperty(OpenmrsConstants.GLOBAL_PROPERTY_FORMS_LOCKED,
"false");
if (Boolean.valueOf(locked)) {
throw new FormsLockedException();
}
}
}