From 0d33fcf118a83d446ee81363c04f226df7618411 Mon Sep 17 00:00:00 2001 From: Vishwas Babu AJ Date: Fri, 9 Nov 2012 19:23:36 +0530 Subject: [PATCH] Adding new image upload services for Data URLs (Webcam integration) --- .../platform/api/ClientsApiResource.java | 57 +++++++++++- .../service/ClientWritePlatformService.java | 4 + ...WritePlatformServiceJpaRepositoryImpl.java | 87 ++++++++++++++----- .../platform/common/Base64EncodedImage.java | 21 +++++ .../mifosng/platform/common/FileUtils.java | 53 ++++++++++- .../ImageDataURLNotValidException.java | 11 +++ 6 files changed, 206 insertions(+), 27 deletions(-) create mode 100755 mifosng-provider/src/main/java/org/mifosng/platform/common/Base64EncodedImage.java create mode 100755 mifosng-provider/src/main/java/org/mifosng/platform/exceptions/ImageDataURLNotValidException.java diff --git a/mifosng-provider/src/main/java/org/mifosng/platform/api/ClientsApiResource.java b/mifosng-provider/src/main/java/org/mifosng/platform/api/ClientsApiResource.java index ab23a3a66..757a483c4 100644 --- a/mifosng-provider/src/main/java/org/mifosng/platform/api/ClientsApiResource.java +++ b/mifosng-provider/src/main/java/org/mifosng/platform/api/ClientsApiResource.java @@ -35,6 +35,7 @@ import org.mifosng.platform.api.infrastructure.PortfolioApiJsonSerializerService import org.mifosng.platform.client.service.ClientReadPlatformService; import org.mifosng.platform.client.service.ClientWritePlatformService; import org.mifosng.platform.common.ApplicationConstants; +import org.mifosng.platform.common.Base64EncodedImage; import org.mifosng.platform.common.FileUtils; import org.mifosng.platform.exceptions.ClientNotFoundException; import org.mifosng.platform.exceptions.ImageNotFoundException; @@ -325,6 +326,14 @@ public class ClientsApiResource { return Response.ok().entity(identifier).build(); } + /** Upload images through multi-part form upload + * @param clientId + * @param fileSize + * @param inputStream + * @param fileDetails + * @param bodyPart + * @return + */ @POST @Path("{clientId}/image") @Consumes({ MediaType.MULTIPART_FORM_DATA }) @@ -355,8 +364,33 @@ public class ClientsApiResource { return Response.ok().entity(entityIdentifier).build(); } + - /**Returns a base 64 encoded client image + /** Upload image as a Data URL (essentially a base64 encoded stream) + * @param clientId + * @param jsonRequestBody + * @return + */ + @POST + @Path("{clientId}/image") + @Consumes({ MediaType.APPLICATION_JSON }) + @Produces({ MediaType.APPLICATION_JSON }) + public Response addNewClientImage( + @PathParam("clientId") final Long clientId, + final String jsonRequestBody) { + + Base64EncodedImage base64EncodedImage = FileUtils + .extractImageFromDataURL(jsonRequestBody); + + EntityIdentifier entityIdentifier = this.clientWritePlatformService + .saveOrUpdateClientImage(clientId, base64EncodedImage); + + // return identifier of the client whose image was updated + return Response.ok().entity(entityIdentifier).build(); + } + + /** + * Returns a base 64 encoded client image * @param clientId * @return */ @@ -413,4 +447,25 @@ public class ClientsApiResource { this.clientWritePlatformService.deleteClientImage(clientId); return Response.ok(new EntityIdentifier(clientId)).build(); } + + + /** + * This method is added only for consistency with other URL patterns + * and for maintaining consistency of usage of the HTTP "verb" + * at the client side + * + * Upload image as a Data URL (essentially a base64 encoded stream) + * @param clientId + * @param jsonRequestBody + * @return + */ + @PUT + @Path("{clientId}/image") + @Consumes({ MediaType.APPLICATION_JSON }) + @Produces({ MediaType.APPLICATION_JSON }) + public Response updateClientImage( + @PathParam("clientId") final Long clientId, + final String jsonRequestBody) { + return addNewClientImage(clientId, jsonRequestBody); + } } \ No newline at end of file diff --git a/mifosng-provider/src/main/java/org/mifosng/platform/client/service/ClientWritePlatformService.java b/mifosng-provider/src/main/java/org/mifosng/platform/client/service/ClientWritePlatformService.java index 493e4d6f3..363731369 100644 --- a/mifosng-provider/src/main/java/org/mifosng/platform/client/service/ClientWritePlatformService.java +++ b/mifosng-provider/src/main/java/org/mifosng/platform/client/service/ClientWritePlatformService.java @@ -6,6 +6,7 @@ import org.mifosng.platform.api.commands.ClientCommand; import org.mifosng.platform.api.commands.ClientIdentifierCommand; import org.mifosng.platform.api.commands.NoteCommand; import org.mifosng.platform.api.data.EntityIdentifier; +import org.mifosng.platform.common.Base64EncodedImage; import org.springframework.security.access.prepost.PreAuthorize; public interface ClientWritePlatformService { @@ -19,6 +20,9 @@ public interface ClientWritePlatformService { @PreAuthorize(value = "hasAnyRole('PORTFOLIO_MANAGEMENT_SUPER_USER_ROLE')") EntityIdentifier saveOrUpdateClientImage(Long clientId, String imageName, InputStream inputStream); + @PreAuthorize(value = "hasAnyRole('PORTFOLIO_MANAGEMENT_SUPER_USER_ROLE')") + EntityIdentifier saveOrUpdateClientImage(Long clientId, Base64EncodedImage encodedImage); + @PreAuthorize(value = "hasAnyRole('PORTFOLIO_MANAGEMENT_SUPER_USER_ROLE')") EntityIdentifier deleteClientImage(Long clientId); diff --git a/mifosng-provider/src/main/java/org/mifosng/platform/client/service/ClientWritePlatformServiceJpaRepositoryImpl.java b/mifosng-provider/src/main/java/org/mifosng/platform/client/service/ClientWritePlatformServiceJpaRepositoryImpl.java index 213885840..7e4fa8cd3 100644 --- a/mifosng-provider/src/main/java/org/mifosng/platform/client/service/ClientWritePlatformServiceJpaRepositoryImpl.java +++ b/mifosng-provider/src/main/java/org/mifosng/platform/client/service/ClientWritePlatformServiceJpaRepositoryImpl.java @@ -16,6 +16,7 @@ import org.mifosng.platform.client.domain.ClientRepository; import org.mifosng.platform.client.domain.Note; import org.mifosng.platform.client.domain.NoteRepository; import org.mifosng.platform.common.ApplicationConstants; +import org.mifosng.platform.common.Base64EncodedImage; import org.mifosng.platform.common.FileUtils; import org.mifosng.platform.exceptions.ClientIdentifierNotFoundException; import org.mifosng.platform.exceptions.ClientNotFoundException; @@ -333,34 +334,14 @@ public class ClientWritePlatformServiceJpaRepositoryImpl implements ClientWriteP String imageName, InputStream inputStream) { try { final Client client = this.clientRepository.findOne(clientId); - if (client == null || client.isDeleted()) { - throw new ClientNotFoundException(clientId); - } - - String imageUploadLocation = FileUtils - .generateImageParentDirectory( - ApplicationConstants.IMAGE_MANAGEMENT_ENTITY.CLIENTS, - clientId); - // delete previous image from the file system - if (StringUtils.isNotEmpty(client.getImageKey())) { - FileUtils.deleteImage( - ApplicationConstants.IMAGE_MANAGEMENT_ENTITY.CLIENTS, - clientId, client.getImageKey()); - } - - /** Recursively create the directory if it does not exist **/ - if (!new File(imageUploadLocation).isDirectory()) { - new File(imageUploadLocation).mkdirs(); - } + String imageUploadLocation = setupForClientImageUpdate(clientId, + client); String imageLocation = FileUtils.saveToFileSystem(inputStream, imageUploadLocation, imageName); - client.setImageKey(imageLocation); - this.clientRepository.save(client); - - return new EntityIdentifier(clientId); + return updateClientImage(clientId, client, imageLocation); } catch (IOException ioe) { logger.error(ioe.getMessage(), ioe); throw new DocumentManagementException(imageName); @@ -380,8 +361,66 @@ public class ClientWritePlatformServiceJpaRepositoryImpl implements ClientWriteP ApplicationConstants.IMAGE_MANAGEMENT_ENTITY.CLIENTS, clientId, client.getImageKey()); } - client.setImageKey(null); + return updateClientImage(clientId, client, null); + } + + @Override + public EntityIdentifier saveOrUpdateClientImage(Long clientId, + Base64EncodedImage encodedImage) { + try { + final Client client = this.clientRepository.findOne(clientId); + String imageUploadLocation = setupForClientImageUpdate(clientId, + client); + + String imageLocation = FileUtils.saveToFileSystem(encodedImage, imageUploadLocation, "image"); + + return updateClientImage(clientId, client, imageLocation); + } catch (IOException ioe) { + logger.error(ioe.getMessage(), ioe); + throw new DocumentManagementException("image"); + } + } + + /** + * @param clientId + * @param client + * @return + */ + private String setupForClientImageUpdate(Long clientId, final Client client) { + if (client == null || client.isDeleted()) { + throw new ClientNotFoundException(clientId); + } + + String imageUploadLocation = FileUtils + .generateImageParentDirectory( + ApplicationConstants.IMAGE_MANAGEMENT_ENTITY.CLIENTS, + clientId); + // delete previous image from the file system + if (StringUtils.isNotEmpty(client.getImageKey())) { + FileUtils.deleteImage( + ApplicationConstants.IMAGE_MANAGEMENT_ENTITY.CLIENTS, + clientId, client.getImageKey()); + } + + /** Recursively create the directory if it does not exist **/ + if (!new File(imageUploadLocation).isDirectory()) { + new File(imageUploadLocation).mkdirs(); + } + return imageUploadLocation; + } + + + /** + * @param clientId + * @param client + * @param imageLocation + * @return + */ + private EntityIdentifier updateClientImage(Long clientId, + final Client client, String imageLocation) { + client.setImageKey(imageLocation); this.clientRepository.save(client); + return new EntityIdentifier(clientId); } diff --git a/mifosng-provider/src/main/java/org/mifosng/platform/common/Base64EncodedImage.java b/mifosng-provider/src/main/java/org/mifosng/platform/common/Base64EncodedImage.java new file mode 100755 index 000000000..908cf6497 --- /dev/null +++ b/mifosng-provider/src/main/java/org/mifosng/platform/common/Base64EncodedImage.java @@ -0,0 +1,21 @@ +package org.mifosng.platform.common; + +public class Base64EncodedImage { + + private final String base64EncodedString; + private final String fileExtension; + + public Base64EncodedImage(String base64EncodedString, String fileExtension) { + this.base64EncodedString = base64EncodedString; + this.fileExtension = fileExtension; + } + + public String getBase64EncodedString() { + return base64EncodedString; + } + + public String getFileExtension() { + return fileExtension; + } + +} diff --git a/mifosng-provider/src/main/java/org/mifosng/platform/common/FileUtils.java b/mifosng-provider/src/main/java/org/mifosng/platform/common/FileUtils.java index fd0563346..6e7027128 100755 --- a/mifosng-provider/src/main/java/org/mifosng/platform/common/FileUtils.java +++ b/mifosng-provider/src/main/java/org/mifosng/platform/common/FileUtils.java @@ -11,12 +11,15 @@ import java.util.Random; import org.mifosng.platform.api.data.ApiParameterError; import org.mifosng.platform.exceptions.DocumentManagementException; +import org.mifosng.platform.exceptions.ImageDataURLNotValidException; import org.mifosng.platform.exceptions.ImageUploadException; import org.mifosng.platform.exceptions.PlatformApiDataValidationException; import org.mifosng.platform.infrastructure.ThreadLocalContextUtil; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import com.lowagie.text.pdf.codec.Base64; + public class FileUtils { private final static Logger logger = LoggerFactory @@ -107,6 +110,25 @@ public class FileUtils { out.close(); return fileLocation; } + + + /** + * @param base64EncodedImage + * @param uploadedFileLocation + * @param fileName + * @return + * @throws IOException + */ + public static String saveToFileSystem(Base64EncodedImage base64EncodedImage, + String uploadedFileLocation, String fileName) throws IOException { + String fileLocation = uploadedFileLocation + File.separator + fileName + base64EncodedImage.getFileExtension(); + OutputStream out = new FileOutputStream(new File(fileLocation)); + byte[] imgBytes = Base64.decode(base64EncodedImage.getBase64EncodedString()); + out.write(imgBytes); + out.flush(); + out.close(); + return fileLocation; + } /** * @param fileSize @@ -137,8 +159,35 @@ public class FileUtils { throw new ImageUploadException(); } } - - + + /** + * Extracts Image from a Data URL + * + * @param mimeType + */ + public static Base64EncodedImage extractImageFromDataURL(String dataURL) { + String fileExtension = ""; + String base64EncodedString = null; + String gifDataURLSnippet = "data:image/gif;base64,"; + String pngDataURLSnippet = "data:image/png;base64,"; + String jpegDataURLSnippet = "data:image/jpeg;base64,"; + + if (dataURL.contains(gifDataURLSnippet)) { + base64EncodedString = dataURL.replaceAll(gifDataURLSnippet, ""); + fileExtension = ".gif"; + } else if (dataURL.contains(pngDataURLSnippet)) { + base64EncodedString = dataURL.replaceAll(pngDataURLSnippet, ""); + fileExtension = ".png"; + } else if (dataURL.contains(jpegDataURLSnippet)) { + base64EncodedString = dataURL.replaceAll(jpegDataURLSnippet, ""); + fileExtension = ".jpeg"; + } else { + throw new ImageDataURLNotValidException(); + } + + return new Base64EncodedImage(base64EncodedString, fileExtension); + } + public static void validateClientImageNotEmpty(String imageFileName) { List dataValidationErrors = new ArrayList(); if (imageFileName == null) { diff --git a/mifosng-provider/src/main/java/org/mifosng/platform/exceptions/ImageDataURLNotValidException.java b/mifosng-provider/src/main/java/org/mifosng/platform/exceptions/ImageDataURLNotValidException.java new file mode 100755 index 000000000..7e8bb7b9f --- /dev/null +++ b/mifosng-provider/src/main/java/org/mifosng/platform/exceptions/ImageDataURLNotValidException.java @@ -0,0 +1,11 @@ +package org.mifosng.platform.exceptions; + +public class ImageDataURLNotValidException extends + AbstractPlatformDomainRuleException { + + public ImageDataURLNotValidException() { + super("error.msg.dataURL.save", + "Only GIF, PNG and JPEG Data URL's are allowed"); + } + +}