Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

ATT-47: Support for concept parameter when saving/fetching attachments. #53

Open
wants to merge 5 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -12,13 +12,15 @@
import org.junit.Before;
import org.junit.Test;
import org.openmrs.Concept;
import org.openmrs.ConceptComplex;
import org.openmrs.Encounter;
import org.openmrs.Obs;
import org.openmrs.Patient;
import org.openmrs.Provider;
import org.openmrs.Visit;
import org.openmrs.api.APIException;
import org.openmrs.module.attachments.obs.Attachment;
import org.openmrs.module.attachments.obs.DefaultAttachmentHandler;
import org.openmrs.module.attachments.obs.TestHelper;
import org.openmrs.test.BaseModuleContextSensitiveTest;
import org.springframework.beans.factory.annotation.Autowired;
Expand Down Expand Up @@ -305,4 +307,27 @@ public void getAttachments_shouldThrowWhenFetchingNonComplexObs() throws Excepti
//
as.getAttachments(patient, encounter, true);
}

@Test
public void getAttachments_shouldReturnAttachmentsAssociatedWithSpecifiedConcept() throws Exception {
//
// setup
//
ConceptComplex conceptComplex = testHelper.createConceptComplex("f4fab86d-4a1d-4245-8aa2-19f49b5ab07a",
"Random files", DefaultAttachmentHandler.class.getSimpleName(), "Random binary files");
Patient patient = ctx.getPatientService().getPatient(2);
Obs obs = testHelper.saveComplexObs(null, conceptComplex, null, 1, 0).get(0);

//
// replay
//
List<Attachment> attachments = as.getAttachments(patient, conceptComplex);

//
// verify
//
Assert.assertEquals(1, attachments.size());
Assert.assertEquals(obs.getUuid(), attachments.get(0).getUuid());

}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

import java.util.List;

import org.openmrs.Concept;
import org.openmrs.Encounter;
import org.openmrs.Patient;
import org.openmrs.Visit;
Expand Down Expand Up @@ -60,5 +61,13 @@ public interface AttachmentsService {
*/
List<Attachment> getAttachments(Patient patient, Visit visit, boolean includeVoided);

/**
* Get a patient's attachments that are associated with a specified concept.
*
* @param concept
* @throws APIException if non-complex obs are mistakenly returned
*/
List<Attachment> getAttachments(Patient patient, Concept concept);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is my main issue, the changes to AttachmentsService are possibly much wider than thought here.

I believe this is where the existing API could be deprecated and a new one is to be introduced that caters for the new concept argument.

Copy link
Member Author

@samuelmale samuelmale May 11, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I believe this is where the existing API could be deprecated and a new one is to be introduced that caters for the new concept argument.

@mks-d Looks like some good time is required for this one; do you think its okay to handle this in a different/followup ticket?


Attachment save(Attachment attachment, String reason);
}
Original file line number Diff line number Diff line change
Expand Up @@ -120,6 +120,21 @@ public List<Attachment> getAttachments(Patient patient, final Visit visit, boole
return attachments;
}

@Override
public List<Attachment> getAttachments(Patient patient, Concept concept) {
List<Obs> obsList = ctx.getObsService().getObservationsByPersonAndConcept(patient, concept);
List<Attachment> attachments = new ArrayList<>();
for (Obs obs : obsList) {
if (!obs.isComplex()) {
throw new APIException(NON_COMPLEX_OBS_ERR);
}
obs = getComplexObs(obs);
attachments.add(new Attachment(obs, ctx.getComplexDataHelper()));
}

return attachments;
}

// Get list of attachment complex concepts
protected List<Concept> getAttachmentConcepts() {
List<String> conceptsComplex = ctx.getConceptComplexList();
Expand Down Expand Up @@ -161,4 +176,5 @@ private Obs getComplexObs(Obs obs) {
String view = ctx.getComplexViewHelper().getView(obs, AttachmentsConstants.ATT_VIEW_THUMBNAIL);
return ctx.getObsService().getComplexObs(obs.getId(), view);
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -9,27 +9,29 @@
*/
package org.openmrs.module.attachments;

import net.coobird.thumbnailator.Thumbnails;
import static org.openmrs.module.attachments.AttachmentsConstants.ContentFamily.IMAGE;
import static org.openmrs.module.attachments.AttachmentsConstants.ContentFamily.OTHER;
import static org.openmrs.module.attachments.AttachmentsContext.getCompressionRatio;

import java.io.IOException;
import java.util.Date;

import javax.imageio.ImageIO;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.openmrs.ConceptComplex;
import org.openmrs.Encounter;
import org.openmrs.Obs;
import org.openmrs.Person;
import org.openmrs.Visit;
import org.openmrs.module.attachments.AttachmentsConstants.ContentFamily;
import org.openmrs.module.attachments.obs.ComplexDataHelper;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.stereotype.Component;
import org.springframework.web.multipart.MultipartFile;

import javax.imageio.ImageIO;

import static org.openmrs.module.attachments.AttachmentsContext.getCompressionRatio;

import java.io.IOException;
import java.util.Date;
import net.coobird.thumbnailator.Thumbnails;

@Component(AttachmentsConstants.COMPONENT_COMPLEXOBS_SAVER)
public class ComplexObsSaver {
Expand All @@ -48,28 +50,29 @@ public class ComplexObsSaver {
@Qualifier(AttachmentsConstants.COMPONENT_VISIT_COMPATIBILITY)
protected VisitCompatibility visitCompatibility;

protected Obs obs = new Obs();

protected ConceptComplex conceptComplex;

public Obs getObs() {
return obs;
}

protected void prepareComplexObs(Visit visit, Person person, Encounter encounter, String fileCaption) {
obs = new Obs(person, conceptComplex,
protected Obs newObs(ConceptComplex conceptComplex, Visit visit, Person person, Encounter encounter, String comment) {
Obs obs = new Obs(person, conceptComplex,
visit == null || visit.getStopDatetime() == null ? new Date() : visit.getStopDatetime(),
encounter != null ? encounter.getLocation() : null);
obs.setEncounter(encounter); // may be null
obs.setComment(fileCaption);
obs.setComment(comment);
return obs;
}

public Obs saveImageAttachment(Visit visit, Person person, Encounter encounter, String fileCaption,
MultipartFile multipartFile, String instructions) throws IOException {
public Obs saveImageObs(Visit visit, Person person, Encounter encounter, String comment, MultipartFile multipartFile,
String instructions) throws IOException {
return saveImageObs(visit, person, encounter, context.getConceptComplex(IMAGE), comment, multipartFile,
instructions);
}

public Obs saveImageObs(Visit visit, Person person, Encounter encounter, ConceptComplex conceptComplex,
String fileCaption, MultipartFile multipartFile, String instructions) throws IOException {

conceptComplex = context.getConceptComplex(ContentFamily.IMAGE);
prepareComplexObs(visit, person, encounter, fileCaption);
if (conceptComplex == null) {
conceptComplex = context.getConceptComplex(IMAGE);
}

Obs obs = newObs(conceptComplex, visit, person, encounter, fileCaption);
Object image = multipartFile.getInputStream();
double compressionRatio = getCompressionRatio(multipartFile.getSize(), 1000000 * context.getMaxStorageFileSize());
if (compressionRatio < 1) {
Expand All @@ -78,18 +81,27 @@ public Obs saveImageAttachment(Visit visit, Person person, Encounter encounter,
obs.setComplexData(
complexDataHelper.build(instructions, multipartFile.getOriginalFilename(), image, multipartFile.getContentType())
.asComplexData());
obs = context.getObsService().saveObs(obs, getClass().toString());
return obs;

return context.getObsService().saveObs(obs, getClass().toString());
}

public Obs saveOtherObs(Visit visit, Person person, Encounter encounter, String comment, MultipartFile multipartFile,
String instructions) throws IOException {
return saveOtherObs(visit, person, encounter, context.getConceptComplex(OTHER), comment, multipartFile,
instructions);
}

public Obs saveOtherAttachment(Visit visit, Person person, Encounter encounter, String fileCaption,
public Obs saveOtherObs(Visit visit, Person person, Encounter encounter, ConceptComplex conceptComplex, String comment,
MultipartFile multipartFile, String instructions) throws IOException {
conceptComplex = context.getConceptComplex(ContentFamily.OTHER);
prepareComplexObs(visit, person, encounter, fileCaption);

if (conceptComplex == null) {
conceptComplex = context.getConceptComplex(OTHER);
}

Obs obs = newObs(conceptComplex, visit, person, encounter, comment);
obs.setComplexData(complexDataHelper.build(instructions, multipartFile.getOriginalFilename(),
multipartFile.getBytes(), multipartFile.getContentType()).asComplexData());
obs = context.getObsService().saveObs(obs, getClass().toString());
return obs;

return context.getObsService().saveObs(obs, getClass().toString());
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,6 @@ public class Attachment extends BaseOpenmrsData implements java.io.Serializable

protected ComplexData complexData = null;


public Attachment() {
}

Expand Down
Original file line number Diff line number Diff line change
@@ -1,13 +1,18 @@
package org.openmrs.module.attachments.obs;

import java.awt.image.BufferedImage;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.Locale;
import java.util.Random;

import javax.imageio.ImageIO;

import org.apache.commons.io.FilenameUtils;
import org.apache.commons.io.IOUtils;
import org.apache.commons.lang.RandomStringUtils;
Expand All @@ -23,6 +28,7 @@
import org.openmrs.Patient;
import org.openmrs.Provider;
import org.openmrs.Visit;
import org.openmrs.api.ConceptService;
import org.openmrs.api.EncounterService;
import org.openmrs.api.context.Context;
import org.openmrs.module.attachments.AttachmentsActivator;
Expand Down Expand Up @@ -182,7 +188,7 @@ public Obs getTestComplexObs() throws IOException {
Encounter encounter = context.getAttachmentEncounter(patient, visit, provider);

String fileCaption = RandomStringUtils.randomAlphabetic(12);
return obsSaver.saveOtherAttachment(visit, patient, encounter, fileCaption, getTestDefaultFile(),
return obsSaver.saveOtherObs(visit, patient, encounter, fileCaption, getTestDefaultFile(),
ValueComplex.INSTRUCTIONS_DEFAULT);
}

Expand All @@ -207,7 +213,7 @@ public Obs saveImageAttachment(String imagePath, String mimeType) throws IOExcep
mimeType, IOUtils.toByteArray(getClass().getClassLoader().getResourceAsStream(imagePath)));

String fileCaption = RandomStringUtils.randomAlphabetic(12);
return obsSaver.saveImageAttachment(visit, patient, encounter, fileCaption, lastSavedMultipartImageFile,
return obsSaver.saveImageObs(visit, patient, encounter, fileCaption, lastSavedMultipartImageFile,
ValueComplex.INSTRUCTIONS_DEFAULT);
}

Expand All @@ -232,7 +238,7 @@ public Obs getTestComplexObsWithoutAssociatedEncounterOrVisit() throws Exception
Patient patient = Context.getPatientService().getPatient(2);

String fileCaption = RandomStringUtils.randomAlphabetic(12);
return obsSaver.saveOtherAttachment(null, patient, null, fileCaption, getTestDefaultFile(),
return obsSaver.saveOtherObs(null, patient, null, fileCaption, getTestDefaultFile(),
ValueComplex.INSTRUCTIONS_DEFAULT);
}

Expand All @@ -257,6 +263,22 @@ public String getTestComplexObsFilePath() {
* @return List of saved attachments/complex obs.
*/
public List<Obs> saveComplexObs(Encounter encounter, int count, int otherCount) throws IOException {
return saveComplexObs(encounter, null, null, count, otherCount);
}

/**
* Boilerplate method to save a collection of complex obs based on the encounter.
*
* @param encounter target encounter for save the complex obs. Leave null to save encounter-less
* complex obs.
* @param conceptComplex The concept that will be associated with attachment obs to be saved.
* @param otherConcept The concept that will be associated with other complex obs to be saved.
* @param count The number of the attachments/complex obs to be saved.
* @param otherCount The number of other complex obs to be saved.
* @return List of saved attachments/complex obs.
*/
public List<Obs> saveComplexObs(Encounter encounter, ConceptComplex conceptComplex, Concept otherConcept, int count,
int otherCount) throws IOException {
List<Obs> obsList = new ArrayList<>();
byte[] randomData = new byte[20];
Patient patient = (encounter == null) ? context.getPatientService().getPatient(2) : encounter.getPatient();
Expand All @@ -270,14 +292,14 @@ public List<Obs> saveComplexObs(Encounter encounter, int count, int otherCount)
String filename = RandomStringUtils.randomAlphabetic(7) + ".ext";
MockMultipartFile multipartRandomFile = new MockMultipartFile(FilenameUtils.getBaseName(filename), filename,
"application/octet-stream", randomData);
obsList.add(obsSaver.saveOtherAttachment(visit, patient, encounter, fileCaption, multipartRandomFile,
obsList.add(obsSaver.saveOtherObs(visit, patient, encounter, conceptComplex, fileCaption, multipartRandomFile,
ValueComplex.INSTRUCTIONS_DEFAULT));
}

// Saves a complex obs as if they had been saved outside of Attachments
for (int i = 0; i < otherCount; i++) {
Obs obs = new Obs();
obs.setConcept(otherConceptComplex);
obs.setConcept(otherConcept != null ? otherConcept : otherConceptComplex);
obs.setObsDatetime(new Date());
obs.setPerson(patient);
obs.setEncounter(encounter);
Expand All @@ -294,4 +316,32 @@ public List<Obs> saveComplexObs(Encounter encounter, int count, int otherCount)
}
return obsList;
}

/**
* Convenience method that constructs and saves a ConceptComplex object.
*/
public ConceptComplex createConceptComplex(String uuid, String name, String handler, String description) {
ConceptService conceptService = Context.getConceptService();
ConceptComplex conceptComplex = new ConceptComplex();
conceptComplex.setUuid(uuid);
conceptComplex.setHandler(handler);
ConceptName conceptName = new ConceptName(name, Locale.ENGLISH);
conceptComplex.setFullySpecifiedName(conceptName);
conceptComplex.setPreferredName(conceptName);
conceptComplex.setConceptClass(conceptService.getConceptClassByName("Question"));
conceptComplex.setDatatype(conceptService.getConceptDatatypeByUuid(ConceptDatatype.COMPLEX_UUID));
conceptComplex.addDescription(new ConceptDescription(description, Locale.ENGLISH));

return (ConceptComplex) conceptService.saveConcept(conceptComplex);
}

public static byte[] loadImageResourceToByteArray(ClassLoader loader, String imageName, String contentType)
throws IOException {
InputStream inputStream = loader.getResourceAsStream(imageName);
BufferedImage img = ImageIO.read(inputStream);
ByteArrayOutputStream baos = new ByteArrayOutputStream();
ImageIO.write(img, contentType, baos);

return baos.toByteArray();
}
}
Loading