MIFOSX-97: repackage document management code.

This commit is contained in:
Keith Woodlock
2012-12-01 16:56:55 +00:00
parent 9e4cc15441
commit 6fe8b41ca9
30 changed files with 894 additions and 1102 deletions
@@ -1,281 +0,0 @@
package org.mifosng.platform.api;
import java.io.File;
import java.io.InputStream;
import java.util.Arrays;
import java.util.Collection;
import java.util.HashSet;
import java.util.Set;
import javax.ws.rs.Consumes;
import javax.ws.rs.DELETE;
import javax.ws.rs.GET;
import javax.ws.rs.HeaderParam;
import javax.ws.rs.POST;
import javax.ws.rs.PUT;
import javax.ws.rs.Path;
import javax.ws.rs.PathParam;
import javax.ws.rs.Produces;
import javax.ws.rs.core.Context;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Response;
import javax.ws.rs.core.Response.ResponseBuilder;
import javax.ws.rs.core.UriInfo;
import org.mifosng.platform.api.commands.DocumentCommand;
import org.mifosng.platform.api.data.DocumentData;
import org.mifosng.platform.documentmanagement.service.DocumentReadPlatformService;
import org.mifosng.platform.documentmanagement.service.DocumentWritePlatformService;
import org.mifosplatform.infrastructure.core.api.ApiConstants;
import org.mifosplatform.infrastructure.core.api.ApiParameterHelper;
import org.mifosplatform.infrastructure.core.api.PortfolioApiJsonSerializerService;
import org.mifosplatform.infrastructure.core.data.EntityIdentifier;
import org.mifosplatform.infrastructure.core.service.FileUtils;
import org.mifosplatform.infrastructure.security.service.PlatformSecurityContext;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Scope;
import org.springframework.stereotype.Component;
import com.sun.jersey.core.header.FormDataContentDisposition;
import com.sun.jersey.multipart.FormDataBodyPart;
import com.sun.jersey.multipart.FormDataParam;
@Path("{entityType}/{entityId}/documents")
@Component
@Scope("singleton")
public class DocumentManagementApiResource {
@Autowired
private DocumentReadPlatformService documentReadPlatformService;
@Autowired
private DocumentWritePlatformService documentWritePlatformService;
@Autowired
private PortfolioApiJsonSerializerService apiJsonSerializerService;
private static final Set<String> typicalResponseParameters = new HashSet<String>(
Arrays.asList("id", "parentEntityType", "parentEntityId", "name",
"fileName", "type", "size", "description"));
private final String SystemEntityType = "DOCUMENT";
@Autowired
private PlatformSecurityContext context;
/**
* @param uriInfo
* @param entityType
* @param entityId
* @return
*/
@GET
@Consumes({ MediaType.APPLICATION_JSON })
@Produces({ MediaType.APPLICATION_JSON })
public String retreiveAllDocuments(@Context final UriInfo uriInfo,
@PathParam("entityType") String entityType,
@PathParam("entityId") Long entityId) {
context.authenticatedUser().validateHasReadPermission(SystemEntityType);
Set<String> responseParameters = ApiParameterHelper
.extractFieldsForResponseIfProvided(uriInfo
.getQueryParameters());
if (responseParameters.isEmpty()) {
responseParameters.addAll(typicalResponseParameters);
}
boolean prettyPrint = ApiParameterHelper.prettyPrint(uriInfo
.getQueryParameters());
Collection<DocumentData> documentDatas = this.documentReadPlatformService
.retrieveAllDocuments(entityType, entityId);
return this.apiJsonSerializerService.serializeDocumentDataToJson(
prettyPrint, responseParameters, documentDatas);
}
/**
* @param entityType
* @param entityId
* @param fileSize
* @param inputStream
* @param fileDetails
* @param bodyPart
* @param name
* @param description
* @return
*/
@POST
@Consumes({ MediaType.MULTIPART_FORM_DATA })
@Produces({ MediaType.APPLICATION_JSON })
public Response createDocument(@PathParam("entityType") String entityType,
@PathParam("entityId") Long entityId,
@HeaderParam("Content-Length") Long fileSize,
@FormDataParam("file") InputStream inputStream,
@FormDataParam("file") FormDataContentDisposition fileDetails,
@FormDataParam("file") FormDataBodyPart bodyPart,
@FormDataParam("name") String name,
@FormDataParam("description") String description) {
FileUtils.validateFileSizeWithinPermissibleRange(fileSize, name, ApiConstants.MAX_FILE_UPLOAD_SIZE_IN_MB);
/**
* TODO: also need to have a backup and stop reading from stream after
* max size is reached to protect against malicious clients
**/
/**
* TODO: need to extract the actual file type and determine if they are
* permissable
**/
DocumentCommand documentCommand = new DocumentCommand(null, null,
entityType, entityId, name, fileDetails.getFileName(),
fileSize, bodyPart.getMediaType().toString(), description, null);
Long documentId = this.documentWritePlatformService.createDocument(
documentCommand, inputStream);
return Response.ok().entity(new EntityIdentifier(documentId)).build();
}
/**
* @param entityType
* @param entityId
* @param documentId
* @param fileSize
* @param inputStream
* @param fileDetails
* @param bodyPart
* @param name
* @param description
* @return
*/
@PUT
@Path("{documentId}")
@Consumes({ MediaType.MULTIPART_FORM_DATA })
@Produces({ MediaType.APPLICATION_JSON })
public Response updateDocument(@PathParam("entityType") String entityType,
@PathParam("entityId") Long entityId,
@PathParam("documentId") Long documentId,
@HeaderParam("Content-Length") Long fileSize,
@FormDataParam("file") InputStream inputStream,
@FormDataParam("file") FormDataContentDisposition fileDetails,
@FormDataParam("file") FormDataBodyPart bodyPart,
@FormDataParam("name") String name,
@FormDataParam("description") String description) {
FileUtils.validateFileSizeWithinPermissibleRange(fileSize, name, ApiConstants.MAX_FILE_UPLOAD_SIZE_IN_MB);
Set<String> modifiedParams = new HashSet<String>();
modifiedParams.add("name");
modifiedParams.add("description");
/***
* Populate Document command based on whether a file has also been
* passed in as a part of the update
***/
DocumentCommand documentCommand = null;
if (inputStream != null && fileDetails.getFileName() != null) {
modifiedParams.add("fileName");
modifiedParams.add("size");
modifiedParams.add("type");
modifiedParams.add("location");
documentCommand = new DocumentCommand(modifiedParams, documentId,
entityType, entityId, name, fileDetails.getFileName(),
fileSize, bodyPart.getMediaType().toString(), description,
null);
} else {
documentCommand = new DocumentCommand(modifiedParams, documentId,
entityType, entityId, name, null, null, null, description,
null);
}
EntityIdentifier identifier = this.documentWritePlatformService
.updateDocument(documentCommand, inputStream);
return Response.ok().entity(identifier).build();
}
/**
* @param entityType
* @param entityId
* @param documentId
* @param uriInfo
* @return
*/
@GET
@Path("{documentId}")
@Consumes({ MediaType.APPLICATION_JSON })
@Produces({ MediaType.APPLICATION_JSON })
public String getDocument(@PathParam("entityType") String entityType,
@PathParam("entityId") Long entityId,
@PathParam("documentId") Long documentId,
@Context final UriInfo uriInfo) {
context.authenticatedUser().validateHasReadPermission(SystemEntityType);
Set<String> responseParameters = ApiParameterHelper
.extractFieldsForResponseIfProvided(uriInfo
.getQueryParameters());
if (responseParameters.isEmpty()) {
responseParameters.addAll(typicalResponseParameters);
}
boolean prettyPrint = ApiParameterHelper.prettyPrint(uriInfo
.getQueryParameters());
DocumentData documentData = this.documentReadPlatformService
.retrieveDocument(entityType, entityId, documentId);
return this.apiJsonSerializerService.serializeDocumentDataToJson(
prettyPrint, responseParameters, documentData);
}
/**
* @param entityType
* @param entityId
* @param documentId
* @return
*/
@GET
@Path("{documentId}/attachment")
@Consumes({ MediaType.APPLICATION_JSON })
@Produces({ MediaType.APPLICATION_JSON })
public Response downloadFile(@PathParam("entityType") String entityType,
@PathParam("entityId") Long entityId,
@PathParam("documentId") Long documentId) {
context.authenticatedUser().validateHasReadPermission(SystemEntityType);
DocumentData documentData = this.documentReadPlatformService
.retrieveDocument(entityType, entityId, documentId);
File file = new File(documentData.getLocation());
ResponseBuilder response = Response.ok(file);
response.header("Content-Disposition", "attachment; filename=\""
+ documentData.getFileName() + "\"");
response.header("Content-Type", documentData.getType());
return response.build();
}
/**
* @param entityType
* @param entityId
* @param documentId
* @return
*/
@DELETE
@Path("{documentId}")
@Consumes({ MediaType.APPLICATION_JSON })
@Produces({ MediaType.APPLICATION_JSON })
public Response deleteDocument(@PathParam("entityType") String entityType,
@PathParam("entityId") Long entityId,
@PathParam("documentId") Long documentId) {
DocumentCommand documentCommand = new DocumentCommand(null, documentId,
entityType, entityId, null, null, null, null, null, null);
EntityIdentifier documentIdentifier = this.documentWritePlatformService
.deleteDocument(documentCommand);
return Response.ok(documentIdentifier).build();
}
}
@@ -1,119 +0,0 @@
package org.mifosng.platform.api.commands;
import java.util.Set;
/**
* Immutable command for creating or updating details of a client identifier.
*/
public class DocumentCommand {
private final Long id;
private final String parentEntityType;
private final Long parentEntityId;
private final String name;
private final String description;
private String fileName;
private Long size;
private String type;
private String location;
private final Set<String> modifiedParameters;
public DocumentCommand(final Set<String> modifiedParameters, final Long id,
final String parentEntityType, final Long parentEntityId,
final String name, final String fileName, Long size, String type,
final String description, String location) {
this.modifiedParameters = modifiedParameters;
this.id = id;
this.parentEntityType = parentEntityType;
this.parentEntityId = parentEntityId;
this.name = name;
this.fileName = fileName;
this.size = size;
this.type = type;
this.description = description;
this.location = location;
}
public Long getId() {
return id;
}
public String getParentEntityType() {
return parentEntityType;
}
public Long getParentEntityId() {
return parentEntityId;
}
public String getName() {
return name;
}
public String getFileName() {
return fileName;
}
public Long getSize() {
return size;
}
public String getType() {
return type;
}
public String getDescription() {
return description;
}
public String getLocation() {
return location;
}
public Set<String> getModifiedParameters() {
return modifiedParameters;
}
public void setFileName(String fileName) {
this.fileName = fileName;
}
public void setSize(Long size) {
this.size = size;
}
public void setType(String type) {
this.type = type;
}
public void setLocation(String location) {
this.location = location;
}
public boolean isNameChanged() {
return this.modifiedParameters.contains("name");
}
public boolean isFileNameChanged() {
return this.modifiedParameters.contains("fileName");
}
public boolean isSizeChanged() {
return this.modifiedParameters.contains("size");
}
public boolean isFileTypeChanged() {
return this.modifiedParameters.contains("type");
}
public boolean isDescriptionChanged() {
return this.modifiedParameters.contains("description");
}
public boolean isLocationChanged() {
return this.modifiedParameters.contains("location");
}
}
@@ -1,67 +0,0 @@
package org.mifosng.platform.api.data;
public class DocumentData {
private final Long id;
private final String parentEntityType;
private final Long parentEntityId;
private final String name;
private final String fileName;
private final Long size;
private final String type;
private final String description;
private final String location;
public DocumentData(final Long id, final String parentEntityType,
final Long parentEntityId, final String name,
final String fileName, final Long size, final String type,
final String description, final String location) {
this.id = id;
this.parentEntityType = parentEntityType;
this.parentEntityId = parentEntityId;
this.name = name;
this.fileName = fileName;
this.size = size;
this.type = type;
this.description = description;
this.location = location;
}
public Long getId() {
return id;
}
public String getParentEntityType() {
return parentEntityType;
}
public Long getParentEntityId() {
return parentEntityId;
}
public String getName() {
return name;
}
public String getFileName() {
return fileName;
}
public Long getSize() {
return size;
}
public String getType() {
return type;
}
public String getDescription() {
return description;
}
public String getLocation() {
return location;
}
}
@@ -1,150 +0,0 @@
package org.mifosng.platform.documentmanagement.domain;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.Table;
import org.apache.commons.lang.StringUtils;
import org.mifosng.platform.api.commands.DocumentCommand;
import org.mifosplatform.infrastructure.core.domain.AbstractAuditableCustom;
import org.mifosplatform.infrastructure.user.domain.AppUser;
@Entity
@Table(name = "m_document")
public class Document extends AbstractAuditableCustom<AppUser, Long> {
@Column(name = "parent_entity_type", length = 50)
private String parentEntityType;
@Column(name = "parent_entity_id", length = 1000)
private Long parentEntityId;
@Column(name = "name", length = 250)
private String name;
@Column(name = "file_name", length = 250)
private String fileName;
@Column(name = "size")
private Long size;
@Column(name = "type", length = 50)
private String type;
@Column(name = "description", length = 1000)
private String description;
@Column(name = "location", length = 500)
private String location;
public Document() {
}
public static Document createNew(String parentEntityType,
Long parentEntityId, String name, String fileName, Long size,
String type, String description, String location) {
return new Document(parentEntityType, parentEntityId, name, fileName,
size, type, description, location);
}
private Document(String parentEntityType, Long parentEntityId, String name,
String fileName, Long size, String type, String description,
String location) {
this.parentEntityType = StringUtils.defaultIfEmpty(parentEntityType,
null);
this.parentEntityId = parentEntityId;
this.name = StringUtils.defaultIfEmpty(name, null);
this.fileName = StringUtils.defaultIfEmpty(fileName, null);
this.size = size;
this.type = StringUtils.defaultIfEmpty(type, null);
this.description = StringUtils.defaultIfEmpty(description, null);
this.location = StringUtils.defaultIfEmpty(location, null);
}
public void update(final DocumentCommand command) {
if (command.isDescriptionChanged()) {
this.description = command.getDescription();
}
if (command.isFileNameChanged()) {
this.fileName = command.getFileName();
}
if (command.isFileTypeChanged()) {
this.type = command.getType();
}
if (command.isLocationChanged()) {
this.location = command.getLocation();
}
if (command.isNameChanged()) {
this.name = command.getName();
}
if (command.isSizeChanged()) {
this.size = command.getSize();
}
}
public String getParentEntityType() {
return parentEntityType;
}
public void setParentEntityType(String parentEntityType) {
this.parentEntityType = parentEntityType;
}
public Long getParentEntityId() {
return parentEntityId;
}
public void setParentEntityId(Long parentEntityId) {
this.parentEntityId = parentEntityId;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getFileName() {
return fileName;
}
public void setFileName(String fileName) {
this.fileName = fileName;
}
public Long getSize() {
return size;
}
public void setSize(Long size) {
this.size = size;
}
public String getType() {
return type;
}
public void setType(String type) {
this.type = type;
}
public String getDescription() {
return description;
}
public void setDescription(String description) {
this.description = description;
}
public String getLocation() {
return location;
}
public void setLocation(String location) {
this.location = location;
}
}
@@ -1,9 +0,0 @@
package org.mifosng.platform.documentmanagement.domain;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.JpaSpecificationExecutor;
public interface DocumentRepository extends
JpaRepository<Document, Long>, JpaSpecificationExecutor<Document> {
// no added behaviour
}
@@ -1,75 +0,0 @@
package org.mifosng.platform.documentmanagement.service;
import java.util.ArrayList;
import java.util.List;
import org.mifosng.platform.api.commands.DocumentCommand;
import org.mifosplatform.infrastructure.core.data.ApiParameterError;
import org.mifosplatform.infrastructure.core.data.DataValidatorBuilder;
import org.mifosplatform.infrastructure.core.exception.PlatformApiDataValidationException;
public class DocumentCommandValidator {
private final DocumentCommand command;
public DocumentCommandValidator(DocumentCommand command) {
this.command = command;
}
public void validateForUpdate() {
List<ApiParameterError> dataValidationErrors = new ArrayList<ApiParameterError>();
DataValidatorBuilder baseDataValidator = new DataValidatorBuilder(
dataValidationErrors).resource("document");
baseDataValidator.reset().parameter("name").value(command.getName())
.ignoreIfNull().notBlank();
baseDataValidator.reset().parameter("size").value(command.getSize())
.ignoreIfNull().integerGreaterThanZero();
baseDataValidator.reset().parameter("fileName")
.value(command.getFileName()).ignoreIfNull().notBlank()
.notExceedingLengthOf(250);
baseDataValidator.reset().parameter("location")
.value(command.getLocation()).ignoreIfNull().notBlank();
baseDataValidator.reset().parameter("description")
.value(command.getName()).ignoreIfNull()
.notExceedingLengthOf(250);
baseDataValidator.reset().anyOfNotNull(command.getName(),
command.getFileName(), command.getDescription(),
command.getLocation(), command.getSize());
if (!dataValidationErrors.isEmpty()) {
throw new PlatformApiDataValidationException(
"validation.msg.validation.errors.exist",
"Validation errors exist.", dataValidationErrors);
}
}
public void validateForCreate() {
List<ApiParameterError> dataValidationErrors = new ArrayList<ApiParameterError>();
DataValidatorBuilder baseDataValidator = new DataValidatorBuilder(
dataValidationErrors).resource("document");
baseDataValidator.reset().parameter("parentEntityType")
.value(command.getParentEntityType()).notBlank()
.notExceedingLengthOf(50);
baseDataValidator.reset().parameter("parentEntityId")
.value(command.getParentEntityId()).integerGreaterThanZero();
baseDataValidator.reset().parameter("name").value(command.getName())
.notBlank().notExceedingLengthOf(250);
baseDataValidator.reset().parameter("size").value(command.getSize())
.integerGreaterThanZero();
baseDataValidator.reset().parameter("fileName")
.value(command.getFileName()).notBlank()
.notExceedingLengthOf(250);
baseDataValidator.reset().parameter("description")
.value(command.getName()).notExceedingLengthOf(250);
if (!dataValidationErrors.isEmpty()) {
throw new PlatformApiDataValidationException(
"validation.msg.validation.errors.exist",
"Validation errors exist.", dataValidationErrors);
}
}
}
@@ -1,15 +0,0 @@
package org.mifosng.platform.documentmanagement.service;
import java.util.Collection;
import org.mifosng.platform.api.data.DocumentData;
public interface DocumentReadPlatformService {
Collection<DocumentData> retrieveAllDocuments(String entityType,
Long entityId);
DocumentData retrieveDocument(String entityType, Long entityId,
Long documentId);
}
@@ -1,107 +0,0 @@
package org.mifosng.platform.documentmanagement.service;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.Collection;
import org.mifosng.platform.api.data.DocumentData;
import org.mifosng.platform.exceptions.DocumentNotFoundException;
import org.mifosplatform.infrastructure.core.domain.JdbcSupport;
import org.mifosplatform.infrastructure.core.service.TenantAwareRoutingDataSource;
import org.mifosplatform.infrastructure.security.service.PlatformSecurityContext;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.dao.EmptyResultDataAccessException;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.core.RowMapper;
import org.springframework.stereotype.Service;
@Service
public class DocumentReadPlatformServiceImpl implements DocumentReadPlatformService {
private final JdbcTemplate jdbcTemplate;
// TODO: use these services to check if passed in entities for document
// management exists (and ensure appropriate errors are thrown)
private final PlatformSecurityContext context;
// private final OfficeReadPlatformService officeReadPlatformService;
// private final ClientReadPlatformService clientReadPlatformService;
// private final LoanReadPlatformService loanReadPlatformService;
// private final StaffReadPlatformService staffReadPlatformService;
// private final AppUserReadPlatformService appUserReadPlatformService;
@Autowired
public DocumentReadPlatformServiceImpl(
final PlatformSecurityContext context,
final TenantAwareRoutingDataSource dataSource
// final OfficeReadPlatformService officeReadPlatformService,
// final AppUserReadPlatformService appUserReadPlatformService,
// final ClientReadPlatformService clientReadPlatformService,
// final LoanReadPlatformService loanReadPlatformService,
// final StaffReadPlatformService staffReadPlatformService
) {
this.context = context;
// this.officeReadPlatformService = officeReadPlatformService;
// this.appUserReadPlatformService = appUserReadPlatformService;
// this.clientReadPlatformService = clientReadPlatformService;
// this.loanReadPlatformService = loanReadPlatformService;
// this.staffReadPlatformService = staffReadPlatformService;
this.jdbcTemplate = new JdbcTemplate(dataSource);
}
@Override
public Collection<DocumentData> retrieveAllDocuments(final String entityType, final Long entityId) {
context.authenticatedUser();
// TODO verify if the entities are valid and a user
// has data
// scope for the particular entities
DocumentMapper mapper = new DocumentMapper();
String sql = "select " + mapper.schema() + " order by d.id";
return this.jdbcTemplate.query(sql, mapper, new Object[] {entityType, entityId});
}
@Override
public DocumentData retrieveDocument(final String entityType, final Long entityId, final Long documentId) {
try {
context.authenticatedUser();
// TODO verify if the entities are valid and a
// user has data
// scope for the particular entities
DocumentMapper mapper = new DocumentMapper();
String sql = "select " + mapper.schema() + " and d.id=? ";
return this.jdbcTemplate.queryForObject(sql, mapper, new Object[] {entityType, entityId, documentId });
} catch (EmptyResultDataAccessException e) {
throw new DocumentNotFoundException(entityType, entityId, documentId);
}
}
private static final class DocumentMapper implements RowMapper<DocumentData> {
public String schema() {
return "d.id as id, d.parent_entity_type as parentEntityType, d.parent_entity_id as parentEntityId, d.name as name, "
+ " d.file_name as fileName, d.size as fileSize, d.type as fileType, "
+ " d.description as description, d.location as location"
+ " from m_document d where d.parent_entity_type=? and d.parent_entity_id=?";
}
@Override
public DocumentData mapRow(final ResultSet rs, @SuppressWarnings("unused") final int rowNum) throws SQLException {
final Long id = JdbcSupport.getLong(rs, "id");
final Long parentEntityId = JdbcSupport.getLong(rs, "parentEntityId");
final Long fileSize = JdbcSupport.getLong(rs, "fileSize");
final String parentEntityType = rs.getString("parentEntityType");
final String name = rs.getString("name");
final String fileName = rs.getString("fileName");
final String fileType = rs.getString("fileType");
final String description = rs.getString("description");
final String location = rs.getString("location");
return new DocumentData(id, parentEntityType, parentEntityId, name,
fileName, fileSize, fileType, description, location);
}
}
}
@@ -1,21 +0,0 @@
package org.mifosng.platform.documentmanagement.service;
import java.io.InputStream;
import org.mifosng.platform.api.commands.DocumentCommand;
import org.mifosplatform.infrastructure.core.data.EntityIdentifier;
import org.springframework.security.access.prepost.PreAuthorize;
public interface DocumentWritePlatformService {
@PreAuthorize(value = "hasAnyRole('ALL_FUNCTIONS', 'PORTFOLIO_MANAGEMENT_SUPER_USER', 'CREATE_m_guarantor_external')")
Long createDocument(DocumentCommand documentCommand, InputStream inputStream);
@PreAuthorize(value = "hasAnyRole('ALL_FUNCTIONS', 'PORTFOLIO_MANAGEMENT_SUPER_USER', 'UPDATE_m_guarantor_external')")
EntityIdentifier updateDocument(DocumentCommand documentCommand,
InputStream inputStream);
@PreAuthorize(value = "hasAnyRole('ALL_FUNCTIONS', 'PORTFOLIO_MANAGEMENT_SUPER_USER', 'DELETE_m_guarantor_external')")
EntityIdentifier deleteDocument(DocumentCommand documentCommand);
}
@@ -1,198 +0,0 @@
package org.mifosng.platform.documentmanagement.service;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import org.mifosng.platform.api.commands.DocumentCommand;
import org.mifosng.platform.documentmanagement.domain.Document;
import org.mifosng.platform.documentmanagement.domain.DocumentRepository;
import org.mifosng.platform.exceptions.DocumentManagementException;
import org.mifosng.platform.exceptions.DocumentNotFoundException;
import org.mifosng.platform.exceptions.InvalidEntityTypeForDocumentManagementException;
import org.mifosplatform.infrastructure.core.data.EntityIdentifier;
import org.mifosplatform.infrastructure.core.exception.PlatformDataIntegrityException;
import org.mifosplatform.infrastructure.core.service.FileUtils;
import org.mifosplatform.infrastructure.security.service.PlatformSecurityContext;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.dao.DataIntegrityViolationException;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
@Service
public class DocumentWritePlatformServiceJpaRepositoryImpl implements
DocumentWritePlatformService {
private final static Logger logger = LoggerFactory.getLogger(DocumentWritePlatformServiceJpaRepositoryImpl.class);
private final PlatformSecurityContext context;
private final DocumentRepository documentRepository;
@Autowired
public DocumentWritePlatformServiceJpaRepositoryImpl(
final PlatformSecurityContext context,
final DocumentRepository documentRepository) {
this.context = context;
this.documentRepository = documentRepository;
}
@Transactional
@Override
public Long createDocument(final DocumentCommand documentCommand, final InputStream inputStream) {
try {
context.authenticatedUser();
DocumentCommandValidator validator = new DocumentCommandValidator(documentCommand);
validateParentEntityType(documentCommand);
validator.validateForCreate();
String fileUploadLocation = FileUtils.generateFileParentDirectory(
documentCommand.getParentEntityType(),
documentCommand.getParentEntityId());
/** Recursively create the directory if it does not exist **/
if (!new File(fileUploadLocation).isDirectory()) {
new File(fileUploadLocation).mkdirs();
}
String fileLocation = FileUtils.saveToFileSystem(inputStream,
fileUploadLocation, documentCommand.getFileName());
Document document = Document.createNew(
documentCommand.getParentEntityType(),
documentCommand.getParentEntityId(),
documentCommand.getName(), documentCommand.getFileName(),
documentCommand.getSize(), documentCommand.getType(),
documentCommand.getDescription(), fileLocation);
this.documentRepository.save(document);
return document.getId();
} catch (DataIntegrityViolationException dve) {
logger.error(dve.getMessage(), dve);
throw new PlatformDataIntegrityException(
"error.msg.document.unknown.data.integrity.issue",
"Unknown data integrity issue with resource.");
} catch (IOException ioe) {
logger.error(ioe.getMessage(), ioe);
throw new DocumentManagementException(documentCommand.getName());
}
}
@Transactional
@Override
public EntityIdentifier updateDocument(final DocumentCommand documentCommand, final InputStream inputStream) {
try {
context.authenticatedUser();
String oldLocation = null;
DocumentCommandValidator validator = new DocumentCommandValidator(documentCommand);
validator.validateForUpdate();
// TODO check if entity id is valid and within data scope for the
// user
Document documentForUpdate = this.documentRepository
.findOne(documentCommand.getId());
if (documentForUpdate == null) {
throw new DocumentNotFoundException(
documentCommand.getParentEntityType(),
documentCommand.getParentEntityId(),
documentCommand.getId());
}
oldLocation = documentForUpdate.getLocation();
// if a new file is also passed in
if (inputStream != null && documentCommand.isFileNameChanged()) {
String fileUploadLocation = FileUtils
.generateFileParentDirectory(
documentCommand.getParentEntityType(),
documentCommand.getParentEntityId());
/** Recursively create the directory if it does not exist **/
if (!new File(fileUploadLocation).isDirectory()) {
new File(fileUploadLocation).mkdirs();
}
// TODO replace file system appender with an Amazon S3 appender
String fileLocation = FileUtils.saveToFileSystem(inputStream,
fileUploadLocation, documentCommand.getFileName());
documentCommand.setLocation(fileLocation);
}
documentForUpdate.update(documentCommand);
if (inputStream != null && documentCommand.isFileNameChanged()) {
// delete previous file
deleteFile(documentCommand.getName(), oldLocation);
}
this.documentRepository.saveAndFlush(documentForUpdate);
return new EntityIdentifier(documentForUpdate.getId());
} catch (DataIntegrityViolationException dve) {
logger.error(dve.getMessage(), dve);
throw new PlatformDataIntegrityException(
"error.msg.document.unknown.data.integrity.issue",
"Unknown data integrity issue with resource.");
} catch (IOException ioe) {
logger.error(ioe.getMessage(), ioe);
throw new DocumentManagementException(documentCommand.getName());
}
}
@Transactional
@Override
public EntityIdentifier deleteDocument(final DocumentCommand documentCommand) {
context.authenticatedUser();
validateParentEntityType(documentCommand);
// TODO: Check document is present under this entity Id
Document document = this.documentRepository.findOne(documentCommand.getId());
if (document == null) {
throw new DocumentNotFoundException(
documentCommand.getParentEntityType(),
documentCommand.getParentEntityId(),
documentCommand.getId());
}
this.documentRepository.delete(document);
deleteFile(document.getName(), document.getLocation());
return new EntityIdentifier(document.getId());
}
private void deleteFile(String documentName, String location) {
File fileToBeDeleted = new File(location);
boolean fileDeleted = fileToBeDeleted.delete();
if (!fileDeleted) {
throw new DocumentManagementException(documentName);
}
}
private void validateParentEntityType(DocumentCommand documentCommand) {
if (!checkValidEntityType(documentCommand.getParentEntityType())) {
throw new InvalidEntityTypeForDocumentManagementException(
documentCommand.getParentEntityType());
}
}
private static boolean checkValidEntityType(final String entityType) {
for (DOCUMENT_MANAGEMENT_ENTITY entities : DOCUMENT_MANAGEMENT_ENTITY.values()) {
if (entities.name().equalsIgnoreCase(entityType)) {
return true;
}
}
return false;
}
/*** Entities for document Management **/
public static enum DOCUMENT_MANAGEMENT_ENTITY {
CLIENTS, CLIENT_IDENTIFIERS, STAFF, LOANS, SAVINGS;
@Override
public String toString() {
return name().toString().toLowerCase();
}
}
}
@@ -1,24 +0,0 @@
package org.mifosng.platform.exceptions;
import org.mifosplatform.infrastructure.core.exception.AbstractPlatformDomainRuleException;
public class DocumentManagementException extends
AbstractPlatformDomainRuleException {
public DocumentManagementException(final String name) {
super("error.msg.document.save", "Error while manipulating file "
+ name + " due to a File system / Amazon S3 issue", name);
}
public DocumentManagementException(final String name, final Long fileSize,
final int maxFileSize) {
super("error.msg.document.file.too.big",
"Unable to save the document with name" + name
+ " since its file Size of " + fileSize / (1024 * 1024)
+ " MB exceeds the max permissable file size of "
+ maxFileSize
+ " MB", name, fileSize);
}
}
@@ -1,14 +0,0 @@
package org.mifosng.platform.exceptions;
import org.mifosplatform.infrastructure.core.exception.AbstractPlatformResourceNotFoundException;
public class DocumentNotFoundException extends
AbstractPlatformResourceNotFoundException {
public DocumentNotFoundException(final String entityType,
final Long entityId, final Long id) {
super("error.msg.document.id.invalid", "Document with identifier " + id
+ " does not exist for the " + entityType + " with Identifier "
+ entityId, id);
}
}
@@ -1,18 +0,0 @@
package org.mifosng.platform.exceptions;
import org.mifosplatform.infrastructure.core.exception.AbstractPlatformResourceNotFoundException;
/**
* A {@link RuntimeException} thrown when document management functionality is
* invoked for invalid Entity Types
*/
public class InvalidEntityTypeForDocumentManagementException extends
AbstractPlatformResourceNotFoundException {
public InvalidEntityTypeForDocumentManagementException(
final String entityType) {
super("error.documentmanagement.entitytype.invalid",
"Document Management is not support for the Entity Type: "
+ entityType, entityType);
}
}
@@ -7,7 +7,6 @@ import java.util.Set;
import org.mifosng.platform.api.data.DepositAccountData;
import org.mifosng.platform.api.data.DepositProductData;
import org.mifosng.platform.api.data.DocumentData;
import org.mifosng.platform.api.data.GroupAccountSummaryCollectionData;
import org.mifosng.platform.api.data.GroupData;
import org.mifosng.platform.api.data.GuarantorData;
@@ -19,6 +18,7 @@ import org.mifosplatform.infrastructure.configuration.data.ConfigurationData;
import org.mifosplatform.infrastructure.core.data.EntityIdentifier;
import org.mifosplatform.infrastructure.dataqueries.data.DatatableData;
import org.mifosplatform.infrastructure.dataqueries.data.GenericResultsetData;
import org.mifosplatform.infrastructure.documentmanagement.data.DocumentData;
import org.mifosplatform.infrastructure.office.data.OfficeData;
import org.mifosplatform.infrastructure.office.data.OfficeTransactionData;
import org.mifosplatform.infrastructure.staff.data.BulkTransferLoanOfficerData;
@@ -5,7 +5,6 @@ import java.util.Set;
import org.mifosng.platform.api.data.DepositAccountData;
import org.mifosng.platform.api.data.DepositProductData;
import org.mifosng.platform.api.data.DocumentData;
import org.mifosng.platform.api.data.GroupAccountSummaryCollectionData;
import org.mifosng.platform.api.data.GroupData;
import org.mifosng.platform.api.data.GuarantorData;
@@ -17,6 +16,7 @@ import org.mifosplatform.infrastructure.configuration.data.ConfigurationData;
import org.mifosplatform.infrastructure.core.data.EntityIdentifier;
import org.mifosplatform.infrastructure.dataqueries.data.DatatableData;
import org.mifosplatform.infrastructure.dataqueries.data.GenericResultsetData;
import org.mifosplatform.infrastructure.documentmanagement.data.DocumentData;
import org.mifosplatform.infrastructure.office.data.OfficeData;
import org.mifosplatform.infrastructure.office.data.OfficeTransactionData;
import org.mifosplatform.infrastructure.staff.data.BulkTransferLoanOfficerData;
@@ -9,12 +9,12 @@ import java.util.ArrayList;
import java.util.List;
import java.util.Random;
import org.mifosng.platform.exceptions.DocumentManagementException;
import org.mifosplatform.infrastructure.core.data.ApiParameterError;
import org.mifosplatform.infrastructure.core.domain.Base64EncodedImage;
import org.mifosplatform.infrastructure.core.exception.ImageDataURLNotValidException;
import org.mifosplatform.infrastructure.core.exception.ImageUploadException;
import org.mifosplatform.infrastructure.core.exception.PlatformApiDataValidationException;
import org.mifosplatform.infrastructure.documentmanagement.exception.DocumentManagementException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -0,0 +1,191 @@
package org.mifosplatform.infrastructure.documentmanagement.api;
import java.io.File;
import java.io.InputStream;
import java.util.Collection;
import java.util.HashSet;
import java.util.Set;
import javax.ws.rs.Consumes;
import javax.ws.rs.DELETE;
import javax.ws.rs.GET;
import javax.ws.rs.HeaderParam;
import javax.ws.rs.POST;
import javax.ws.rs.PUT;
import javax.ws.rs.Path;
import javax.ws.rs.PathParam;
import javax.ws.rs.Produces;
import javax.ws.rs.core.Context;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Response;
import javax.ws.rs.core.Response.ResponseBuilder;
import javax.ws.rs.core.UriInfo;
import org.mifosplatform.infrastructure.core.api.ApiConstants;
import org.mifosplatform.infrastructure.core.api.ApiParameterHelper;
import org.mifosplatform.infrastructure.core.api.PortfolioApiJsonSerializerService;
import org.mifosplatform.infrastructure.core.data.EntityIdentifier;
import org.mifosplatform.infrastructure.core.service.FileUtils;
import org.mifosplatform.infrastructure.documentmanagement.command.DocumentCommand;
import org.mifosplatform.infrastructure.documentmanagement.data.DocumentData;
import org.mifosplatform.infrastructure.documentmanagement.service.DocumentReadPlatformService;
import org.mifosplatform.infrastructure.documentmanagement.service.DocumentWritePlatformService;
import org.mifosplatform.infrastructure.security.service.PlatformSecurityContext;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Scope;
import org.springframework.stereotype.Component;
import com.sun.jersey.core.header.FormDataContentDisposition;
import com.sun.jersey.multipart.FormDataBodyPart;
import com.sun.jersey.multipart.FormDataParam;
@Path("{entityType}/{entityId}/documents")
@Component
@Scope("singleton")
public class DocumentManagementApiResource {
private final String SystemEntityType = "DOCUMENT";
private final PlatformSecurityContext context;
private final DocumentReadPlatformService documentReadPlatformService;
private final DocumentWritePlatformService documentWritePlatformService;
private final PortfolioApiJsonSerializerService apiJsonSerializerService;
@Autowired
public DocumentManagementApiResource(final PlatformSecurityContext context,
final DocumentReadPlatformService documentReadPlatformService, final DocumentWritePlatformService documentWritePlatformService,
final PortfolioApiJsonSerializerService apiJsonSerializerService) {
this.context = context;
this.documentReadPlatformService = documentReadPlatformService;
this.documentWritePlatformService = documentWritePlatformService;
this.apiJsonSerializerService = apiJsonSerializerService;
}
@GET
@Consumes({ MediaType.APPLICATION_JSON })
@Produces({ MediaType.APPLICATION_JSON })
public String retreiveAllDocuments(@Context final UriInfo uriInfo, @PathParam("entityType") String entityType,
@PathParam("entityId") Long entityId) {
context.authenticatedUser().validateHasReadPermission(SystemEntityType);
final Set<String> responseParameters = ApiParameterHelper.extractFieldsForResponseIfProvided(uriInfo.getQueryParameters());
final boolean prettyPrint = ApiParameterHelper.prettyPrint(uriInfo.getQueryParameters());
final Collection<DocumentData> documentDatas = this.documentReadPlatformService.retrieveAllDocuments(entityType, entityId);
return this.apiJsonSerializerService.serializeDocumentDataToJson(prettyPrint, responseParameters, documentDatas);
}
@POST
@Consumes({ MediaType.MULTIPART_FORM_DATA })
@Produces({ MediaType.APPLICATION_JSON })
public Response createDocument(@PathParam("entityType") String entityType, @PathParam("entityId") Long entityId,
@HeaderParam("Content-Length") Long fileSize, @FormDataParam("file") InputStream inputStream,
@FormDataParam("file") FormDataContentDisposition fileDetails, @FormDataParam("file") FormDataBodyPart bodyPart,
@FormDataParam("name") String name, @FormDataParam("description") String description) {
FileUtils.validateFileSizeWithinPermissibleRange(fileSize, name, ApiConstants.MAX_FILE_UPLOAD_SIZE_IN_MB);
/**
* TODO: also need to have a backup and stop reading from stream after
* max size is reached to protect against malicious clients
**/
/**
* TODO: need to extract the actual file type and determine if they are
* permissable
**/
DocumentCommand documentCommand = new DocumentCommand(null, null, entityType, entityId, name, fileDetails.getFileName(), fileSize,
bodyPart.getMediaType().toString(), description, null);
Long documentId = this.documentWritePlatformService.createDocument(documentCommand, inputStream);
return Response.ok().entity(new EntityIdentifier(documentId)).build();
}
@PUT
@Path("{documentId}")
@Consumes({ MediaType.MULTIPART_FORM_DATA })
@Produces({ MediaType.APPLICATION_JSON })
public Response updateDocument(@PathParam("entityType") String entityType, @PathParam("entityId") Long entityId,
@PathParam("documentId") Long documentId, @HeaderParam("Content-Length") Long fileSize,
@FormDataParam("file") InputStream inputStream, @FormDataParam("file") FormDataContentDisposition fileDetails,
@FormDataParam("file") FormDataBodyPart bodyPart, @FormDataParam("name") String name,
@FormDataParam("description") String description) {
FileUtils.validateFileSizeWithinPermissibleRange(fileSize, name, ApiConstants.MAX_FILE_UPLOAD_SIZE_IN_MB);
Set<String> modifiedParams = new HashSet<String>();
modifiedParams.add("name");
modifiedParams.add("description");
/***
* Populate Document command based on whether a file has also been
* passed in as a part of the update
***/
DocumentCommand documentCommand = null;
if (inputStream != null && fileDetails.getFileName() != null) {
modifiedParams.add("fileName");
modifiedParams.add("size");
modifiedParams.add("type");
modifiedParams.add("location");
documentCommand = new DocumentCommand(modifiedParams, documentId, entityType, entityId, name, fileDetails.getFileName(),
fileSize, bodyPart.getMediaType().toString(), description, null);
} else {
documentCommand = new DocumentCommand(modifiedParams, documentId, entityType, entityId, name, null, null, null, description,
null);
}
EntityIdentifier identifier = this.documentWritePlatformService.updateDocument(documentCommand, inputStream);
return Response.ok().entity(identifier).build();
}
@GET
@Path("{documentId}")
@Consumes({ MediaType.APPLICATION_JSON })
@Produces({ MediaType.APPLICATION_JSON })
public String getDocument(@PathParam("entityType") String entityType, @PathParam("entityId") Long entityId,
@PathParam("documentId") Long documentId, @Context final UriInfo uriInfo) {
context.authenticatedUser().validateHasReadPermission(SystemEntityType);
final Set<String> responseParameters = ApiParameterHelper.extractFieldsForResponseIfProvided(uriInfo.getQueryParameters());
final boolean prettyPrint = ApiParameterHelper.prettyPrint(uriInfo.getQueryParameters());
final DocumentData documentData = this.documentReadPlatformService.retrieveDocument(entityType, entityId, documentId);
return this.apiJsonSerializerService.serializeDocumentDataToJson(prettyPrint, responseParameters, documentData);
}
@GET
@Path("{documentId}/attachment")
@Consumes({ MediaType.APPLICATION_JSON })
@Produces({ MediaType.APPLICATION_JSON })
public Response downloadFile(@PathParam("entityType") String entityType, @PathParam("entityId") Long entityId,
@PathParam("documentId") Long documentId) {
context.authenticatedUser().validateHasReadPermission(SystemEntityType);
DocumentData documentData = this.documentReadPlatformService.retrieveDocument(entityType, entityId, documentId);
File file = new File(documentData.fileLocation());
ResponseBuilder response = Response.ok(file);
response.header("Content-Disposition", "attachment; filename=\"" + documentData.fileName() + "\"");
response.header("Content-Type", documentData.contentType());
return response.build();
}
@DELETE
@Path("{documentId}")
@Consumes({ MediaType.APPLICATION_JSON })
@Produces({ MediaType.APPLICATION_JSON })
public Response deleteDocument(@PathParam("entityType") String entityType, @PathParam("entityId") Long entityId,
@PathParam("documentId") Long documentId) {
DocumentCommand documentCommand = new DocumentCommand(null, documentId, entityType, entityId, null, null, null, null, null, null);
EntityIdentifier documentIdentifier = this.documentWritePlatformService.deleteDocument(documentCommand);
return Response.ok(documentIdentifier).build();
}
}
@@ -0,0 +1,117 @@
package org.mifosplatform.infrastructure.documentmanagement.command;
import java.util.Set;
/**
* Immutable command for creating or updating details of a client identifier.
*/
public class DocumentCommand {
private final Long id;
private final String parentEntityType;
private final Long parentEntityId;
private final String name;
private final String description;
private String fileName;
private Long size;
private String type;
private String location;
private final Set<String> modifiedParameters;
public DocumentCommand(final Set<String> modifiedParameters, final Long id, final String parentEntityType, final Long parentEntityId,
final String name, final String fileName, Long size, String type, final String description, String location) {
this.modifiedParameters = modifiedParameters;
this.id = id;
this.parentEntityType = parentEntityType;
this.parentEntityId = parentEntityId;
this.name = name;
this.fileName = fileName;
this.size = size;
this.type = type;
this.description = description;
this.location = location;
}
public Long getId() {
return id;
}
public String getParentEntityType() {
return parentEntityType;
}
public Long getParentEntityId() {
return parentEntityId;
}
public String getName() {
return name;
}
public String getFileName() {
return fileName;
}
public Long getSize() {
return size;
}
public String getType() {
return type;
}
public String getDescription() {
return description;
}
public String getLocation() {
return location;
}
public Set<String> getModifiedParameters() {
return modifiedParameters;
}
public void setFileName(String fileName) {
this.fileName = fileName;
}
public void setSize(Long size) {
this.size = size;
}
public void setType(String type) {
this.type = type;
}
public void setLocation(String location) {
this.location = location;
}
public boolean isNameChanged() {
return this.modifiedParameters.contains("name");
}
public boolean isFileNameChanged() {
return this.modifiedParameters.contains("fileName");
}
public boolean isSizeChanged() {
return this.modifiedParameters.contains("size");
}
public boolean isFileTypeChanged() {
return this.modifiedParameters.contains("type");
}
public boolean isDescriptionChanged() {
return this.modifiedParameters.contains("description");
}
public boolean isLocationChanged() {
return this.modifiedParameters.contains("location");
}
}
@@ -0,0 +1,50 @@
package org.mifosplatform.infrastructure.documentmanagement.command;
import java.util.ArrayList;
import java.util.List;
import org.mifosplatform.infrastructure.core.data.ApiParameterError;
import org.mifosplatform.infrastructure.core.data.DataValidatorBuilder;
import org.mifosplatform.infrastructure.core.exception.PlatformApiDataValidationException;
public class DocumentCommandValidator {
private final DocumentCommand command;
public DocumentCommandValidator(DocumentCommand command) {
this.command = command;
}
public void validateForUpdate() {
List<ApiParameterError> dataValidationErrors = new ArrayList<ApiParameterError>();
DataValidatorBuilder baseDataValidator = new DataValidatorBuilder(dataValidationErrors).resource("document");
baseDataValidator.reset().parameter("name").value(command.getName()).ignoreIfNull().notBlank();
baseDataValidator.reset().parameter("size").value(command.getSize()).ignoreIfNull().integerGreaterThanZero();
baseDataValidator.reset().parameter("fileName").value(command.getFileName()).ignoreIfNull().notBlank().notExceedingLengthOf(250);
baseDataValidator.reset().parameter("location").value(command.getLocation()).ignoreIfNull().notBlank();
baseDataValidator.reset().parameter("description").value(command.getName()).ignoreIfNull().notExceedingLengthOf(250);
baseDataValidator.reset().anyOfNotNull(command.getName(), command.getFileName(), command.getDescription(), command.getLocation(),
command.getSize());
if (!dataValidationErrors.isEmpty()) { throw new PlatformApiDataValidationException("validation.msg.validation.errors.exist",
"Validation errors exist.", dataValidationErrors); }
}
public void validateForCreate() {
List<ApiParameterError> dataValidationErrors = new ArrayList<ApiParameterError>();
DataValidatorBuilder baseDataValidator = new DataValidatorBuilder(dataValidationErrors).resource("document");
baseDataValidator.reset().parameter("parentEntityType").value(command.getParentEntityType()).notBlank().notExceedingLengthOf(50);
baseDataValidator.reset().parameter("parentEntityId").value(command.getParentEntityId()).integerGreaterThanZero();
baseDataValidator.reset().parameter("name").value(command.getName()).notBlank().notExceedingLengthOf(250);
baseDataValidator.reset().parameter("size").value(command.getSize()).integerGreaterThanZero();
baseDataValidator.reset().parameter("fileName").value(command.getFileName()).notBlank().notExceedingLengthOf(250);
baseDataValidator.reset().parameter("description").value(command.getName()).notExceedingLengthOf(250);
if (!dataValidationErrors.isEmpty()) { throw new PlatformApiDataValidationException("validation.msg.validation.errors.exist",
"Validation errors exist.", dataValidationErrors); }
}
}
@@ -0,0 +1,48 @@
package org.mifosplatform.infrastructure.documentmanagement.data;
/**
* Immutable data object represent document being managed on platform.
*/
public class DocumentData {
@SuppressWarnings("unused")
private final Long id;
@SuppressWarnings("unused")
private final String parentEntityType;
@SuppressWarnings("unused")
private final Long parentEntityId;
@SuppressWarnings("unused")
private final String name;
private final String fileName;
@SuppressWarnings("unused")
private final Long size;
private final String type;
@SuppressWarnings("unused")
private final String description;
private final String location;
public DocumentData(final Long id, final String parentEntityType, final Long parentEntityId, final String name, final String fileName,
final Long size, final String type, final String description, final String location) {
this.id = id;
this.parentEntityType = parentEntityType;
this.parentEntityId = parentEntityId;
this.name = name;
this.fileName = fileName;
this.size = size;
this.type = type;
this.description = description;
this.location = location;
}
public String contentType() {
return this.type;
}
public String fileName() {
return this.fileName;
}
public String fileLocation() {
return this.location;
}
}
@@ -0,0 +1,145 @@
package org.mifosplatform.infrastructure.documentmanagement.domain;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.Table;
import org.apache.commons.lang.StringUtils;
import org.mifosplatform.infrastructure.core.domain.AbstractAuditableCustom;
import org.mifosplatform.infrastructure.documentmanagement.command.DocumentCommand;
import org.mifosplatform.infrastructure.user.domain.AppUser;
@Entity
@Table(name = "m_document")
public class Document extends AbstractAuditableCustom<AppUser, Long> {
@Column(name = "parent_entity_type", length = 50)
private String parentEntityType;
@Column(name = "parent_entity_id", length = 1000)
private Long parentEntityId;
@Column(name = "name", length = 250)
private String name;
@Column(name = "file_name", length = 250)
private String fileName;
@Column(name = "size")
private Long size;
@Column(name = "type", length = 50)
private String type;
@Column(name = "description", length = 1000)
private String description;
@Column(name = "location", length = 500)
private String location;
public Document() {}
public static Document createNew(String parentEntityType, Long parentEntityId, String name, String fileName, Long size, String type,
String description, String location) {
return new Document(parentEntityType, parentEntityId, name, fileName, size, type, description, location);
}
private Document(String parentEntityType, Long parentEntityId, String name, String fileName, Long size, String type,
String description, String location) {
this.parentEntityType = StringUtils.defaultIfEmpty(parentEntityType, null);
this.parentEntityId = parentEntityId;
this.name = StringUtils.defaultIfEmpty(name, null);
this.fileName = StringUtils.defaultIfEmpty(fileName, null);
this.size = size;
this.type = StringUtils.defaultIfEmpty(type, null);
this.description = StringUtils.defaultIfEmpty(description, null);
this.location = StringUtils.defaultIfEmpty(location, null);
}
public void update(final DocumentCommand command) {
if (command.isDescriptionChanged()) {
this.description = command.getDescription();
}
if (command.isFileNameChanged()) {
this.fileName = command.getFileName();
}
if (command.isFileTypeChanged()) {
this.type = command.getType();
}
if (command.isLocationChanged()) {
this.location = command.getLocation();
}
if (command.isNameChanged()) {
this.name = command.getName();
}
if (command.isSizeChanged()) {
this.size = command.getSize();
}
}
public String getParentEntityType() {
return parentEntityType;
}
public void setParentEntityType(String parentEntityType) {
this.parentEntityType = parentEntityType;
}
public Long getParentEntityId() {
return parentEntityId;
}
public void setParentEntityId(Long parentEntityId) {
this.parentEntityId = parentEntityId;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getFileName() {
return fileName;
}
public void setFileName(String fileName) {
this.fileName = fileName;
}
public Long getSize() {
return size;
}
public void setSize(Long size) {
this.size = size;
}
public String getType() {
return type;
}
public void setType(String type) {
this.type = type;
}
public String getDescription() {
return description;
}
public void setDescription(String description) {
this.description = description;
}
public String getLocation() {
return location;
}
public void setLocation(String location) {
this.location = location;
}
}
@@ -0,0 +1,8 @@
package org.mifosplatform.infrastructure.documentmanagement.domain;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.JpaSpecificationExecutor;
public interface DocumentRepository extends JpaRepository<Document, Long>, JpaSpecificationExecutor<Document> {
// no added behaviour
}
@@ -0,0 +1,16 @@
package org.mifosplatform.infrastructure.documentmanagement.exception;
import org.mifosplatform.infrastructure.core.exception.AbstractPlatformDomainRuleException;
public class DocumentManagementException extends AbstractPlatformDomainRuleException {
public DocumentManagementException(final String name) {
super("error.msg.document.save", "Error while manipulating file " + name + " due to a File system / Amazon S3 issue", name);
}
public DocumentManagementException(final String name, final Long fileSize, final int maxFileSize) {
super("error.msg.document.file.too.big", "Unable to save the document with name" + name + " since its file Size of " + fileSize
/ (1024 * 1024) + " MB exceeds the max permissable file size of " + maxFileSize + " MB", name, fileSize);
}
}
@@ -0,0 +1,11 @@
package org.mifosplatform.infrastructure.documentmanagement.exception;
import org.mifosplatform.infrastructure.core.exception.AbstractPlatformResourceNotFoundException;
public class DocumentNotFoundException extends AbstractPlatformResourceNotFoundException {
public DocumentNotFoundException(final String entityType, final Long entityId, final Long id) {
super("error.msg.document.id.invalid", "Document with identifier " + id + " does not exist for the " + entityType
+ " with Identifier " + entityId, id);
}
}
@@ -0,0 +1,15 @@
package org.mifosplatform.infrastructure.documentmanagement.exception;
import org.mifosplatform.infrastructure.core.exception.AbstractPlatformResourceNotFoundException;
/**
* A {@link RuntimeException} thrown when document management functionality is
* invoked for invalid Entity Types
*/
public class InvalidEntityTypeForDocumentManagementException extends AbstractPlatformResourceNotFoundException {
public InvalidEntityTypeForDocumentManagementException(final String entityType) {
super("error.documentmanagement.entitytype.invalid", "Document Management is not support for the Entity Type: " + entityType,
entityType);
}
}
@@ -0,0 +1,12 @@
package org.mifosplatform.infrastructure.documentmanagement.service;
import java.util.Collection;
import org.mifosplatform.infrastructure.documentmanagement.data.DocumentData;
public interface DocumentReadPlatformService {
Collection<DocumentData> retrieveAllDocuments(String entityType, Long entityId);
DocumentData retrieveDocument(String entityType, Long entityId, Long documentId);
}
@@ -0,0 +1,85 @@
package org.mifosplatform.infrastructure.documentmanagement.service;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.Collection;
import org.mifosplatform.infrastructure.core.domain.JdbcSupport;
import org.mifosplatform.infrastructure.core.service.TenantAwareRoutingDataSource;
import org.mifosplatform.infrastructure.documentmanagement.data.DocumentData;
import org.mifosplatform.infrastructure.documentmanagement.exception.DocumentNotFoundException;
import org.mifosplatform.infrastructure.security.service.PlatformSecurityContext;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.dao.EmptyResultDataAccessException;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.core.RowMapper;
import org.springframework.stereotype.Service;
@Service
public class DocumentReadPlatformServiceImpl implements DocumentReadPlatformService {
private final JdbcTemplate jdbcTemplate;
private final PlatformSecurityContext context;
@Autowired
public DocumentReadPlatformServiceImpl(final PlatformSecurityContext context, final TenantAwareRoutingDataSource dataSource) {
this.context = context;
this.jdbcTemplate = new JdbcTemplate(dataSource);
}
@Override
public Collection<DocumentData> retrieveAllDocuments(final String entityType, final Long entityId) {
context.authenticatedUser();
// TODO verify if the entities are valid and a user
// has data
// scope for the particular entities
DocumentMapper mapper = new DocumentMapper();
String sql = "select " + mapper.schema() + " order by d.id";
return this.jdbcTemplate.query(sql, mapper, new Object[] { entityType, entityId });
}
@Override
public DocumentData retrieveDocument(final String entityType, final Long entityId, final Long documentId) {
try {
context.authenticatedUser();
// TODO verify if the entities are valid and a
// user has data
// scope for the particular entities
DocumentMapper mapper = new DocumentMapper();
String sql = "select " + mapper.schema() + " and d.id=? ";
return this.jdbcTemplate.queryForObject(sql, mapper, new Object[] { entityType, entityId, documentId });
} catch (EmptyResultDataAccessException e) {
throw new DocumentNotFoundException(entityType, entityId, documentId);
}
}
private static final class DocumentMapper implements RowMapper<DocumentData> {
public String schema() {
return "d.id as id, d.parent_entity_type as parentEntityType, d.parent_entity_id as parentEntityId, d.name as name, "
+ " d.file_name as fileName, d.size as fileSize, d.type as fileType, "
+ " d.description as description, d.location as location"
+ " from m_document d where d.parent_entity_type=? and d.parent_entity_id=?";
}
@Override
public DocumentData mapRow(final ResultSet rs, @SuppressWarnings("unused") final int rowNum) throws SQLException {
final Long id = JdbcSupport.getLong(rs, "id");
final Long parentEntityId = JdbcSupport.getLong(rs, "parentEntityId");
final Long fileSize = JdbcSupport.getLong(rs, "fileSize");
final String parentEntityType = rs.getString("parentEntityType");
final String name = rs.getString("name");
final String fileName = rs.getString("fileName");
final String fileType = rs.getString("fileType");
final String description = rs.getString("description");
final String location = rs.getString("location");
return new DocumentData(id, parentEntityType, parentEntityId, name, fileName, fileSize, fileType, description, location);
}
}
}
@@ -0,0 +1,20 @@
package org.mifosplatform.infrastructure.documentmanagement.service;
import java.io.InputStream;
import org.mifosplatform.infrastructure.core.data.EntityIdentifier;
import org.mifosplatform.infrastructure.documentmanagement.command.DocumentCommand;
import org.springframework.security.access.prepost.PreAuthorize;
public interface DocumentWritePlatformService {
@PreAuthorize(value = "hasAnyRole('ALL_FUNCTIONS', 'PORTFOLIO_MANAGEMENT_SUPER_USER', 'CREATE_m_guarantor_external')")
Long createDocument(DocumentCommand documentCommand, InputStream inputStream);
@PreAuthorize(value = "hasAnyRole('ALL_FUNCTIONS', 'PORTFOLIO_MANAGEMENT_SUPER_USER', 'UPDATE_m_guarantor_external')")
EntityIdentifier updateDocument(DocumentCommand documentCommand, InputStream inputStream);
@PreAuthorize(value = "hasAnyRole('ALL_FUNCTIONS', 'PORTFOLIO_MANAGEMENT_SUPER_USER', 'DELETE_m_guarantor_external')")
EntityIdentifier deleteDocument(DocumentCommand documentCommand);
}
@@ -0,0 +1,172 @@
package org.mifosplatform.infrastructure.documentmanagement.service;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import org.mifosplatform.infrastructure.core.data.EntityIdentifier;
import org.mifosplatform.infrastructure.core.exception.PlatformDataIntegrityException;
import org.mifosplatform.infrastructure.core.service.FileUtils;
import org.mifosplatform.infrastructure.documentmanagement.command.DocumentCommand;
import org.mifosplatform.infrastructure.documentmanagement.command.DocumentCommandValidator;
import org.mifosplatform.infrastructure.documentmanagement.domain.Document;
import org.mifosplatform.infrastructure.documentmanagement.domain.DocumentRepository;
import org.mifosplatform.infrastructure.documentmanagement.exception.DocumentManagementException;
import org.mifosplatform.infrastructure.documentmanagement.exception.DocumentNotFoundException;
import org.mifosplatform.infrastructure.documentmanagement.exception.InvalidEntityTypeForDocumentManagementException;
import org.mifosplatform.infrastructure.security.service.PlatformSecurityContext;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.dao.DataIntegrityViolationException;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
@Service
public class DocumentWritePlatformServiceJpaRepositoryImpl implements DocumentWritePlatformService {
private final static Logger logger = LoggerFactory.getLogger(DocumentWritePlatformServiceJpaRepositoryImpl.class);
private final PlatformSecurityContext context;
private final DocumentRepository documentRepository;
@Autowired
public DocumentWritePlatformServiceJpaRepositoryImpl(final PlatformSecurityContext context, final DocumentRepository documentRepository) {
this.context = context;
this.documentRepository = documentRepository;
}
@Transactional
@Override
public Long createDocument(final DocumentCommand documentCommand, final InputStream inputStream) {
try {
context.authenticatedUser();
DocumentCommandValidator validator = new DocumentCommandValidator(documentCommand);
validateParentEntityType(documentCommand);
validator.validateForCreate();
String fileUploadLocation = FileUtils.generateFileParentDirectory(documentCommand.getParentEntityType(),
documentCommand.getParentEntityId());
/** Recursively create the directory if it does not exist **/
if (!new File(fileUploadLocation).isDirectory()) {
new File(fileUploadLocation).mkdirs();
}
String fileLocation = FileUtils.saveToFileSystem(inputStream, fileUploadLocation, documentCommand.getFileName());
Document document = Document.createNew(documentCommand.getParentEntityType(), documentCommand.getParentEntityId(),
documentCommand.getName(), documentCommand.getFileName(), documentCommand.getSize(), documentCommand.getType(),
documentCommand.getDescription(), fileLocation);
this.documentRepository.save(document);
return document.getId();
} catch (DataIntegrityViolationException dve) {
logger.error(dve.getMessage(), dve);
throw new PlatformDataIntegrityException("error.msg.document.unknown.data.integrity.issue",
"Unknown data integrity issue with resource.");
} catch (IOException ioe) {
logger.error(ioe.getMessage(), ioe);
throw new DocumentManagementException(documentCommand.getName());
}
}
@Transactional
@Override
public EntityIdentifier updateDocument(final DocumentCommand documentCommand, final InputStream inputStream) {
try {
context.authenticatedUser();
String oldLocation = null;
DocumentCommandValidator validator = new DocumentCommandValidator(documentCommand);
validator.validateForUpdate();
// TODO check if entity id is valid and within data scope for the
// user
Document documentForUpdate = this.documentRepository.findOne(documentCommand.getId());
if (documentForUpdate == null) { throw new DocumentNotFoundException(documentCommand.getParentEntityType(),
documentCommand.getParentEntityId(), documentCommand.getId()); }
oldLocation = documentForUpdate.getLocation();
// if a new file is also passed in
if (inputStream != null && documentCommand.isFileNameChanged()) {
String fileUploadLocation = FileUtils.generateFileParentDirectory(documentCommand.getParentEntityType(),
documentCommand.getParentEntityId());
/** Recursively create the directory if it does not exist **/
if (!new File(fileUploadLocation).isDirectory()) {
new File(fileUploadLocation).mkdirs();
}
// TODO replace file system appender with an Amazon S3 appender
String fileLocation = FileUtils.saveToFileSystem(inputStream, fileUploadLocation, documentCommand.getFileName());
documentCommand.setLocation(fileLocation);
}
documentForUpdate.update(documentCommand);
if (inputStream != null && documentCommand.isFileNameChanged()) {
// delete previous file
deleteFile(documentCommand.getName(), oldLocation);
}
this.documentRepository.saveAndFlush(documentForUpdate);
return new EntityIdentifier(documentForUpdate.getId());
} catch (DataIntegrityViolationException dve) {
logger.error(dve.getMessage(), dve);
throw new PlatformDataIntegrityException("error.msg.document.unknown.data.integrity.issue",
"Unknown data integrity issue with resource.");
} catch (IOException ioe) {
logger.error(ioe.getMessage(), ioe);
throw new DocumentManagementException(documentCommand.getName());
}
}
@Transactional
@Override
public EntityIdentifier deleteDocument(final DocumentCommand documentCommand) {
context.authenticatedUser();
validateParentEntityType(documentCommand);
// TODO: Check document is present under this entity Id
Document document = this.documentRepository.findOne(documentCommand.getId());
if (document == null) { throw new DocumentNotFoundException(documentCommand.getParentEntityType(),
documentCommand.getParentEntityId(), documentCommand.getId()); }
this.documentRepository.delete(document);
deleteFile(document.getName(), document.getLocation());
return new EntityIdentifier(document.getId());
}
private void deleteFile(String documentName, String location) {
File fileToBeDeleted = new File(location);
boolean fileDeleted = fileToBeDeleted.delete();
if (!fileDeleted) { throw new DocumentManagementException(documentName); }
}
private void validateParentEntityType(DocumentCommand documentCommand) {
if (!checkValidEntityType(documentCommand.getParentEntityType())) { throw new InvalidEntityTypeForDocumentManagementException(
documentCommand.getParentEntityType()); }
}
private static boolean checkValidEntityType(final String entityType) {
for (DOCUMENT_MANAGEMENT_ENTITY entities : DOCUMENT_MANAGEMENT_ENTITY.values()) {
if (entities.name().equalsIgnoreCase(entityType)) { return true; }
}
return false;
}
/*** Entities for document Management **/
public static enum DOCUMENT_MANAGEMENT_ENTITY {
CLIENTS, CLIENT_IDENTIFIERS, STAFF, LOANS, SAVINGS;
@Override
public String toString() {
return name().toString().toLowerCase();
}
}
}
@@ -5,7 +5,6 @@ import java.io.IOException;
import java.io.InputStream;
import org.apache.commons.lang.StringUtils;
import org.mifosng.platform.exceptions.DocumentManagementException;
import org.mifosplatform.infrastructure.codes.domain.CodeValue;
import org.mifosplatform.infrastructure.codes.domain.CodeValueRepository;
import org.mifosplatform.infrastructure.codes.exception.CodeValueNotFoundException;
@@ -14,6 +13,7 @@ import org.mifosplatform.infrastructure.core.data.EntityIdentifier;
import org.mifosplatform.infrastructure.core.domain.Base64EncodedImage;
import org.mifosplatform.infrastructure.core.exception.PlatformDataIntegrityException;
import org.mifosplatform.infrastructure.core.service.FileUtils;
import org.mifosplatform.infrastructure.documentmanagement.exception.DocumentManagementException;
import org.mifosplatform.infrastructure.office.domain.Office;
import org.mifosplatform.infrastructure.office.domain.OfficeRepository;
import org.mifosplatform.infrastructure.office.exception.OfficeNotFoundException;