minor changes to client images and updates to documentation

This commit is contained in:
Vishwas Babu AJ
2013-03-19 01:36:46 +05:30
parent d1896d43b1
commit c85e10cfab
10 changed files with 509 additions and 140 deletions
+259
View File
@@ -203,6 +203,26 @@
<td><a href="#client_identifiers_update">Update a Client Identifier</a></td>
<td><a href="#client_identifiers_delete">Delete a Client Identifier</a></td>
</tr>
<tr class="alt">
<td><a href="#client_images">Client Images</a></td>
<td>clients/{clientId}/images</td>
<td><a href="#client_images_create">Upload an Image for a Client (as DATA URI)</a></td>
<td><a href="#client_images_retrieve">Get Client Image (DATA URI)</a></td>
<td><a href="#client_images_update">Update Client Image (DATA URI)</a></td>
<td><a href="#client_images_delete">Delete Client Image</a></td>
</tr>
<tr>
<td></td>
<td></td>
<td><a href="#client_images_create_form">
Upload an Image for a Client (Multi-part form data)</a>
</td>
<td><a href="#client_images_retrieve_binary">Get Client Image (Binary file)</a></td>
<td><a href="#client_images_update_form">
Update Client Image (Multi-part form data)</a>
</td>
<td></td>
</tr>
</table>
</div>
</div>
@@ -2297,6 +2317,225 @@ No Request Body:
</div>
</div>
<!-- start of Client Images API docs-->
<a id="client_images" name="client_images" class="old-syle-anchor">&nbsp;</a>
<div class="method-section">
<div class="method-description">
<h3>Client Images</h3>
<p>The current API provides support for the addition of a single
image for a Client<br/>
Allowed formats: JPEG (.jpg or .jpeg), GIF (.gif) and PNG (.png)
</p>
<p> The API supports two different Approaches for manipulating Client Images
<ul>
<li><a href="http://en.wikipedia.org/wiki/Data_URI_scheme" target="_blank"> Data URI's: </a>
For easier manipulation by Javascript clients etc in supported Browsers.
</li>
<li><a href="http://www.w3.org/TR/html401/interact/forms.html#h-17.13.4.2">
Multi-part form data:</a>
Images can be uploaded using Multi part forms and downloaded as regular binary files
</li>
<br/>
</div>
</div>
<a id="client_images_retrieve" name="client_images_retrieve"
class="old-syle-anchor">&nbsp;</a>
<div class="method-section">
<div class="method-description">
<h4>Get Client Image (DATA URI)</h4>
<p>Example Requests:</p>
<div class=apiClick>clients/1/images</div>
<br>
</div>
<div class="method-example">
<code class="method-declaration">
GET https://DomainName/api/v1/clients/{clientId}/images
</code>
<code class="method-response">
data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAoAAAAKCAYAAACNMs+9AAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJ
bWFnZVJlYWR5ccllPAAAAJ1JREFUeNpi+P//PwMIA4E9EG8E4idQDGLbw+WhiiqA+D8OXAFVAzbp
DxBvB2JLIGaGYkuoGEjOhhFIHAbij0BdPgxYACMj42ogJQpifwBiXSDeC8JIbt4LxSC5DyxQjTeB
+BeaYb+Q5EBOAVutCzMJHUNNPADzzDokiYdAfAmJvwLkGeTgWQfyKZICS6hYBTwc0QL8ORSjBDhA
gAEAOg13B6R/SAgAAAAASUVORK5CYII=
</code>
</div>
</div>
<a id="client_images_retrieve_binary" name="client_images_retrieve_binary"
class="old-syle-anchor">&nbsp;</a>
<div class="method-section">
<div class="method-description">
<h4>Get Client Image (Binary file)</h4>
</div>
<div class="method-example">
<code class="method-declaration">
GET https://DomainName/api/v1/clients/{clientId}/images
</code>
<code class="method-request">
POST clients/1/images
Accept: application/octet-stream
</code>
<code class="method-response">
<i>Not shown</i>: The corresponding binary (image) file
</code>
</div>
</div>
<a id="client_images_create" name="client_images_create" class="old-syle-anchor">&nbsp;</a>
<div class="method-section">
<div class="method-description">
<h4>Upload an Image for a Client (Data URI)</h4>
</div>
<div class="method-example">
<code class="method-declaration">
POST https://DomainName/api/v1/clients/{clientId}/images
</code>
<code class="method-request">
POST clients/1/images
Content-Type: text/plain
Request Body:
data:image/png;base64,iVBORw0KGgoAA
AANSUhEUgAAABAAAAAQAQMAAAAlPW0iAAAABlBMVEUAAAD///+l2Z/dAAAAM0l
EQVR4nGP4/5/h/1+G/58ZDrAz3D/McH8yw83NDDeNGe4Ug9C9zwz3gVLMDA/A6
P9/AFGGFyjOXZtQAAAAAElFTkSuQmCC
</code>
<code class="method-response">
{
"resourceId": 1,
"changes": {},
"resourceIdentifier": "1"
}
</code>
</div>
</div>
<a id="client_images_create_form" name="client_images_create_form" class="old-syle-anchor">&nbsp;</a>
<div class="method-section">
<div class="method-description">
<h4>Upload an Image for a Client (Multi-part Form data)</h4>
<table class=matrixHeading>
<tr class="matrixHeadingBG">
<td><div class="mifosXHeading2">Mandatory Fields</div></td>
</tr>
<tr class=alt>
<td>file
</td>
</tr>
</table>
<p>
The form should contain a required named body part with the
name "file". <br/> <br/>
If you are using a HTML form, a snippet like
<small> &lt;input type="file" name="file"&gt;&lt;/input&gt; </small>
can be used for uploading the image file
</p>
</div>
<div class="method-example">
<code class="method-declaration">
POST https://DomainName/api/v1/clients/{clientId}/images
</code>
<code class="method-request">
POST clients/1/images
Content-Type: multipart/form-data
Request Body: <i>Not shown</i>
</code>
<code class="method-response">
{
"resourceId": 1,
"changes": {},
"resourceIdentifier": "1"
}
</code>
</div>
</div>
<a id="client_images_update" name="client_images_update" class="old-syle-anchor">&nbsp;</a>
<div class="method-section">
<div class="method-description">
<h4>Update a Client Image (Data URI)</h4>
</div>
<div class="method-example">
<code class="method-declaration">
PUT https://DomainName/api/v1/clients/{clientId}/images
</code>
<code class="method-request">
PUT clients/1/images
Content-Type: text/plain
Request Body:
data:image/png;base64,iVBORw0KGgoAA
AANSUhEUgAAABAAAAAQAQMAAAAlPW0iAAAABlBMVEUAAAD///+l2Z/dAAAAM0l
EQVR4nGP4/5/h/1+G/58ZDrAz3D/McH8yw83NDDeNGe4Ug9C9zwz3gVLMDA/A6
P9/AFGGFyjOXZtQAAAAAElFTkSuQmCC
</code>
<code class="method-response">
{
"resourceId": 1,
"changes": {},
"resourceIdentifier": "1"
}
</code>
</div>
</div>
<a id="client_images_update_form" name="client_images_update_form" class="old-syle-anchor">&nbsp;</a>
<div class="method-section">
<div class="method-description">
<h4>Update a Client Image (Multi-part Form data)</h4>
<table class=matrixHeading>
<tr class="matrixHeadingBG">
<td><div class="mifosXHeading2">Mandatory Fields</div></td>
</tr>
<tr class=alt>
<td>file
</td>
</tr>
</table>
<p>
The form should contain a required named body part with the
name "file". <br/> <br/>
If you are using a HTML form, a snippet like
<small> &lt;input type="file" name="file"&gt;&lt;/input&gt; </small>
can be used for uploading the image file
</p>
</div>
<div class="method-example">
<code class="method-declaration">
PUT https://DomainName/api/v1/clients/{clientId}/images
</code>
<code class="method-request">
PUT clients/1/images
Content-Type: multipart/form-data
Request Body: <i>Not shown</i>
</code>
</div>
</div>
<a id="client_images_delete" name="client_images_delete" class="old-syle-anchor">&nbsp;</a>
<div class="method-section">
<div class="method-description">
<h4>Delete a Client Image</h4>
</div>
<div class="method-example">
<code class="method-declaration">
DELETE https://DomainName/api/v1/clients/{clientId}/images
</code>
<code class="method-request">
DELETE clients/1/images
Content-Type: application/json
No Request Body:
</code>
<code class="method-response">
{
"resourceId": 1,
"changes": {},
"resourceIdentifier": "1"
}
</code>
</div>
</div>
<!-- start of Groups api docs -->
<a id="groups" name="groups" class="old-syle-anchor">&nbsp;</a>
@@ -11619,6 +11858,26 @@ Content-Type: application/json
<td><a href="#client_identifiers_update">Update a Client Identifier</a></td>
<td><a href="#client_identifiers_delete">Delete a Client Identifier</a></td>
</tr>
<tr>
<td><a href="#client_images">Client Images</a></td>
<td>clients/{clientId}/images</td>
<td><a href="#client_images_create">Upload an Image for a Client (as DATA URI)</a></td>
<td><a href="#client_images_retrieve">Get Client Image (DATA URI)</a></td>
<td><a href="#client_images_update">Update Client Image (DATA URI)</a></td>
<td><a href="#client_images_delete">Delete Client Image</a></td>
</tr>
<tr class="alt">
<td></td>
<td></td>
<td><a href="#client_images_create_form">
Upload an Image for a Client (Multi-part form data)</a>
</td>
<td><a href="#client_images_retrieve_binary">Get Client Image (Binary file)</a></td>
<td><a href="#client_images_update_form">
Update Client Image (Multi-part form data)</a>
</td>
<td></td>
</tr>
<!-- loans api full matrix-->
<tr>
<td><a href="#loans">Loans</a></td>
@@ -14,6 +14,7 @@ import java.util.ArrayList;
import java.util.List;
import java.util.Random;
import org.apache.commons.lang.StringUtils;
import org.mifosplatform.infrastructure.core.data.ApiParameterError;
import org.mifosplatform.infrastructure.core.domain.Base64EncodedImage;
import org.mifosplatform.infrastructure.core.exception.ImageDataURLNotValidException;
@@ -31,6 +32,49 @@ public class FileUtils {
public static final String MIFOSX_BASE_DIR = System.getProperty("user.home") + File.separator + ".mifosx";
public static enum IMAGE_MIME_TYPE {
GIF("image/gif"), JPEG("image/jpeg"), PNG("image/png");
private final String value;
private IMAGE_MIME_TYPE(final String value) {
this.value = value;
}
public String getValue() {
return this.value;
}
};
public static enum IMAGE_FILE_EXTENSION {
GIF(".gif"), JPEG(".jpeg"), JPG(".jpg"), PNG(".png");
private final String value;
private IMAGE_FILE_EXTENSION(final String value) {
this.value = value;
}
public String getValue() {
return this.value;
}
};
public static enum IMAGE_DATA_URI_SUFFIX {
GIF("data:" + IMAGE_MIME_TYPE.GIF.getValue() + ";base64,"), JPEG("data:" + IMAGE_MIME_TYPE.JPEG.getValue() + ";base64,"), PNG(
"data:" + IMAGE_MIME_TYPE.PNG.getValue() + ";base64,");
private final String value;
private IMAGE_DATA_URI_SUFFIX(final String value) {
this.value = value;
}
public String getValue() {
return this.value;
}
};
public static Random random = new Random();
/**
@@ -138,7 +182,8 @@ public class FileUtils {
* @param mimeType
*/
public static void validateImageMimeType(String mimeType) {
if (!(mimeType.equalsIgnoreCase("image/gif") || mimeType.equalsIgnoreCase("image/jpeg") || mimeType.equalsIgnoreCase("image/png"))) { throw new ImageUploadException(); }
if (!(mimeType.equalsIgnoreCase(IMAGE_MIME_TYPE.GIF.getValue()) || mimeType.equalsIgnoreCase(IMAGE_MIME_TYPE.JPEG.getValue()) || mimeType
.equalsIgnoreCase(IMAGE_MIME_TYPE.PNG.getValue()))) { throw new ImageUploadException(); }
}
/**
@@ -149,19 +194,15 @@ public class FileUtils {
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";
if (StringUtils.startsWith(dataURL, IMAGE_DATA_URI_SUFFIX.GIF.getValue())) {
base64EncodedString = dataURL.replaceAll(IMAGE_DATA_URI_SUFFIX.GIF.getValue(), "");
fileExtension = IMAGE_FILE_EXTENSION.GIF.getValue();
} else if (StringUtils.startsWith(dataURL, IMAGE_DATA_URI_SUFFIX.PNG.getValue())) {
base64EncodedString = dataURL.replaceAll(IMAGE_DATA_URI_SUFFIX.PNG.getValue(), "");
fileExtension = IMAGE_FILE_EXTENSION.PNG.getValue();
} else if (StringUtils.startsWith(dataURL, IMAGE_DATA_URI_SUFFIX.JPEG.getValue())) {
base64EncodedString = dataURL.replaceAll(IMAGE_DATA_URI_SUFFIX.JPEG.getValue(), "");
fileExtension = IMAGE_FILE_EXTENSION.JPEG.getValue();
} else {
throw new ImageDataURLNotValidException();
}
@@ -11,14 +11,14 @@ public class LoanChargeCannotBeDeletedException extends AbstractPlatformDomainRu
/*** enum of reasons of why Loan Charge cannot be waived **/
public static enum LOAN_CHARGE_CANNOT_BE_DELETED_REASON {
ALREADY_PAID, ALREADY_WAIVED, LOAN_APPROVED;
ALREADY_PAID, ALREADY_WAIVED, LOAN_NOT_IN_SUBMITTED_AND_PENDING_APPROVAL_STAGE;
public String errorMessage() {
if (name().toString().equalsIgnoreCase("ALREADY_PAID")) {
return "This loan charge has been partially/completely paid";
} else if (name().toString().equalsIgnoreCase("ALREADY_WAIVED")) {
return "This loan charge has already been waived";
} else if (name().toString().equalsIgnoreCase("LOAN_APPROVED")) { return "This charge cannot be deleted as the loan it is associated with has already been approved"; }
} else if (name().toString().equalsIgnoreCase("LOAN_NOT_IN_SUBMITTED_AND_PENDING_APPROVAL_STAGE")) { return "This charge cannot be deleted as the loan it is associated with is not in submitted and pending approval stage"; }
return name().toString();
}
@@ -27,7 +27,7 @@ public class LoanChargeCannotBeDeletedException extends AbstractPlatformDomainRu
return "error.msg.loan.charge.already.paid";
} else if (name().toString().equalsIgnoreCase("ALREADY_WAIVED")) {
return "error.msg.loan.charge.already.waived";
} else if (name().toString().equalsIgnoreCase("LOAN_APPROVED")) { return "error.msg.loan.charge.associated.loan.already.approved"; }
} else if (name().toString().equalsIgnoreCase("LOAN_NOT_IN_SUBMITTED_AND_PENDING_APPROVAL_STAGE")) { return "error.msg.loan.charge.associated.loan.not.in.submitted.and.pending.approval.stage"; }
return name().toString();
}
}
@@ -0,0 +1,38 @@
/**
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
* You can obtain one at http://mozilla.org/MPL/2.0/.
*/
package org.mifosplatform.portfolio.charge.exception;
import org.mifosplatform.infrastructure.core.exception.AbstractPlatformDomainRuleException;
public class LoanChargeCannotBeUpdatedException extends AbstractPlatformDomainRuleException {
/*** enum of reasons of why Loan Charge cannot be waived **/
public static enum LOAN_CHARGE_CANNOT_BE_UPDATED_REASON {
ALREADY_PAID, ALREADY_WAIVED, LOAN_NOT_IN_SUBMITTED_AND_PENDING_APPROVAL_STAGE;
public String errorMessage() {
if (name().toString().equalsIgnoreCase("ALREADY_PAID")) {
return "This loan charge has been partially/completely paid";
} else if (name().toString().equalsIgnoreCase("ALREADY_WAIVED")) {
return "This loan charge has already been waived";
} else if (name().toString().equalsIgnoreCase("LOAN_NOT_IN_SUBMITTED_AND_PENDING_APPROVAL_STAGE")) { return "This charge cannot be updated as the loan it is associated with is not in submitted and pending approval stage"; }
return name().toString();
}
public String errorCode() {
if (name().toString().equalsIgnoreCase("ALREADY_PAID")) {
return "error.msg.loan.charge.already.paid";
} else if (name().toString().equalsIgnoreCase("ALREADY_WAIVED")) {
return "error.msg.loan.charge.already.waived";
} else if (name().toString().equalsIgnoreCase("LOAN_NOT_IN_SUBMITTED_AND_PENDING_APPROVAL_STAGE")) { return "error.msg.loan.charge.associated.loan.not.in.submitted.and.pending.approval.stage"; }
return name().toString();
}
}
public LoanChargeCannotBeUpdatedException(final LOAN_CHARGE_CANNOT_BE_UPDATED_REASON reason, final Long loanChargeId) {
super(reason.errorCode(), reason.errorMessage(), loanChargeId);
}
}
@@ -5,6 +5,7 @@
*/
package org.mifosplatform.portfolio.client.api;
import java.io.File;
import java.io.InputStream;
import javax.ws.rs.Consumes;
@@ -17,12 +18,18 @@ import javax.ws.rs.Path;
import javax.ws.rs.PathParam;
import javax.ws.rs.Produces;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Response;
import javax.ws.rs.core.Response.ResponseBuilder;
import org.apache.commons.lang.StringUtils;
import org.mifosplatform.infrastructure.core.api.ApiConstants;
import org.mifosplatform.infrastructure.core.data.CommandProcessingResult;
import org.mifosplatform.infrastructure.core.domain.Base64EncodedImage;
import org.mifosplatform.infrastructure.core.serialization.DefaultToApiJsonSerializer;
import org.mifosplatform.infrastructure.core.service.FileUtils;
import org.mifosplatform.infrastructure.core.service.FileUtils.IMAGE_DATA_URI_SUFFIX;
import org.mifosplatform.infrastructure.core.service.FileUtils.IMAGE_FILE_EXTENSION;
import org.mifosplatform.infrastructure.core.service.FileUtils.IMAGE_MIME_TYPE;
import org.mifosplatform.infrastructure.security.service.PlatformSecurityContext;
import org.mifosplatform.portfolio.client.data.ClientData;
import org.mifosplatform.portfolio.client.exception.ImageNotFoundException;
@@ -42,128 +49,146 @@ import com.sun.jersey.multipart.FormDataParam;
@Scope("singleton")
public class ClientImagesApiResource {
private final PlatformSecurityContext context;
private final ClientReadPlatformService clientReadPlatformService;
private final ClientWritePlatformService clientWritePlatformService;
private final DefaultToApiJsonSerializer<ClientData> toApiJsonSerializer;
private final PlatformSecurityContext context;
private final ClientReadPlatformService clientReadPlatformService;
private final ClientWritePlatformService clientWritePlatformService;
private final DefaultToApiJsonSerializer<ClientData> toApiJsonSerializer;
@Autowired
public ClientImagesApiResource(final PlatformSecurityContext context,
final ClientReadPlatformService readPlatformService,
final ClientWritePlatformService clientWritePlatformService,
final DefaultToApiJsonSerializer<ClientData> toApiJsonSerializer) {
this.context = context;
this.clientReadPlatformService = readPlatformService;
this.clientWritePlatformService = clientWritePlatformService;
this.toApiJsonSerializer = toApiJsonSerializer;
}
@Autowired
public ClientImagesApiResource(final PlatformSecurityContext context, final ClientReadPlatformService readPlatformService,
final ClientWritePlatformService clientWritePlatformService, final DefaultToApiJsonSerializer<ClientData> toApiJsonSerializer) {
this.context = context;
this.clientReadPlatformService = readPlatformService;
this.clientWritePlatformService = clientWritePlatformService;
this.toApiJsonSerializer = toApiJsonSerializer;
}
/**
* Upload images through multi-part form upload
*/
@POST
@Consumes({ MediaType.MULTIPART_FORM_DATA })
@Produces({ MediaType.APPLICATION_JSON })
public String addNewClientImage(
@PathParam("clientId") final Long clientId,
@HeaderParam("Content-Length") final Long fileSize,
@FormDataParam("file") final InputStream inputStream,
@FormDataParam("file") final FormDataContentDisposition fileDetails,
@FormDataParam("file") final FormDataBodyPart bodyPart) {
/**
* Upload images through multi-part form upload
*/
@POST
@Consumes({ MediaType.MULTIPART_FORM_DATA })
@Produces({ MediaType.APPLICATION_JSON })
public String addNewClientImage(@PathParam("clientId") final Long clientId, @HeaderParam("Content-Length") final Long fileSize,
@FormDataParam("file") final InputStream inputStream, @FormDataParam("file") final FormDataContentDisposition fileDetails,
@FormDataParam("file") final FormDataBodyPart bodyPart) {
// TODO: vishwas might need more advances validation (like reading magic
// number) for handling malicious clients
// and clients not setting mime type
FileUtils.validateClientImageNotEmpty(fileDetails.getFileName());
FileUtils.validateImageMimeType(bodyPart.getMediaType().toString());
FileUtils.validateFileSizeWithinPermissibleRange(fileSize,
fileDetails.getFileName(),
ApiConstants.MAX_FILE_UPLOAD_SIZE_IN_MB);
// TODO: vishwas might need more advances validation (like reading magic
// number) for handling malicious clients
// and clients not setting mime type
FileUtils.validateClientImageNotEmpty(fileDetails.getFileName());
FileUtils.validateImageMimeType(bodyPart.getMediaType().toString());
FileUtils.validateFileSizeWithinPermissibleRange(fileSize, fileDetails.getFileName(), ApiConstants.MAX_FILE_UPLOAD_SIZE_IN_MB);
final CommandProcessingResult result = this.clientWritePlatformService
.saveOrUpdateClientImage(clientId, fileDetails.getFileName(),
inputStream);
final CommandProcessingResult result = this.clientWritePlatformService.saveOrUpdateClientImage(clientId, fileDetails.getFileName(),
inputStream);
return this.toApiJsonSerializer.serialize(result);
}
return this.toApiJsonSerializer.serialize(result);
}
/**
* Upload image as a Data URL (essentially a base64 encoded stream)
*/
@POST
@Consumes({ MediaType.APPLICATION_JSON })
@Produces({ MediaType.APPLICATION_JSON })
public String addNewClientImage(@PathParam("clientId") final Long clientId,
final String jsonRequestBody) {
/**
* Upload image as a Data URL (essentially a base64 encoded stream)
*/
@POST
@Consumes({ MediaType.TEXT_PLAIN, MediaType.TEXT_HTML, MediaType.APPLICATION_JSON })
@Produces({ MediaType.APPLICATION_JSON })
public String addNewClientImage(@PathParam("clientId") final Long clientId, final String jsonRequestBody) {
final Base64EncodedImage base64EncodedImage = FileUtils
.extractImageFromDataURL(jsonRequestBody);
final Base64EncodedImage base64EncodedImage = FileUtils.extractImageFromDataURL(jsonRequestBody);
final CommandProcessingResult result = this.clientWritePlatformService
.saveOrUpdateClientImage(clientId, base64EncodedImage);
final CommandProcessingResult result = this.clientWritePlatformService.saveOrUpdateClientImage(clientId, base64EncodedImage);
return this.toApiJsonSerializer.serialize(result);
}
return this.toApiJsonSerializer.serialize(result);
}
/**
* Returns a base 64 encoded client image
*/
@GET
@Consumes({ MediaType.APPLICATION_JSON })
@Produces({ MediaType.APPLICATION_JSON })
public String retrieveClientImage(@PathParam("clientId") final Long clientId) {
/**
* Returns a base 64 encoded client image Data URI
*/
@GET
@Consumes({ MediaType.TEXT_PLAIN, MediaType.TEXT_HTML, MediaType.APPLICATION_JSON })
@Produces({ MediaType.TEXT_PLAIN })
public String retrieveClientImage(@PathParam("clientId") final Long clientId) {
context.authenticatedUser().validateHasReadPermission("CLIENTIMAGE");
context.authenticatedUser().validateHasReadPermission("CLIENTIMAGE");
final ClientData clientData = this.clientReadPlatformService
.retrieveIndividualClient(clientId);
final ClientData clientData = this.clientReadPlatformService.retrieveIndividualClient(clientId);
if (clientData.imageKeyDoesNotExist()) {
throw new ImageNotFoundException("clients", clientId);
}
if (clientData.imageKeyDoesNotExist()) { throw new ImageNotFoundException("clients", clientId); }
return Base64.encodeFromFile(clientData.imageKey());
}
// TODO: Need a better way of determining image type
String imageDataURISuffix = IMAGE_DATA_URI_SUFFIX.JPEG.getValue();
if (StringUtils.endsWith(clientData.imageKey(), IMAGE_FILE_EXTENSION.GIF.getValue())) {
imageDataURISuffix = IMAGE_DATA_URI_SUFFIX.GIF.getValue();
} else if (StringUtils.endsWith(clientData.imageKey(), IMAGE_FILE_EXTENSION.PNG.getValue())) {
imageDataURISuffix = IMAGE_DATA_URI_SUFFIX.PNG.getValue();
}
/**
* 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
*/
@PUT
@Consumes({ MediaType.MULTIPART_FORM_DATA })
@Produces({ MediaType.APPLICATION_JSON })
public String updateClientImage(
@PathParam("clientId") final Long clientId,
@HeaderParam("Content-Length") final Long fileSize,
@FormDataParam("file") final InputStream inputStream,
@FormDataParam("file") final FormDataContentDisposition fileDetails,
@FormDataParam("file") final FormDataBodyPart bodyPart) {
String clientImageAsBase64Text = imageDataURISuffix + Base64.encodeFromFile(clientData.imageKey());
return addNewClientImage(clientId, fileSize, inputStream, fileDetails,
bodyPart);
}
return clientImageAsBase64Text;
}
@DELETE
@Consumes({ MediaType.APPLICATION_JSON })
@Produces({ MediaType.APPLICATION_JSON })
public String deleteClientImage(@PathParam("clientId") final Long clientId) {
this.clientWritePlatformService.deleteClientImage(clientId);
@GET
@Consumes({ MediaType.APPLICATION_JSON })
@Produces({ MediaType.APPLICATION_OCTET_STREAM })
public Response downloadClientImage(@PathParam("clientId") final Long clientId) {
return this.toApiJsonSerializer.serialize(new CommandProcessingResult(
clientId));
}
context.authenticatedUser().validateHasReadPermission("CLIENTIMAGE");
final ClientData clientData = this.clientReadPlatformService.retrieveIndividualClient(clientId);
if (clientData.imageKeyDoesNotExist()) { throw new ImageNotFoundException("clients", clientId); }
File image = new File(clientData.imageKey());
String imageName = image.getName();
ResponseBuilder response = Response.ok(image);
response.header("Content-Disposition", "attachment; filename=\"" + imageName + "\"");
// TODO: Need a better way of determining image type
// determine image type
String contentType = IMAGE_MIME_TYPE.JPEG.getValue();
if (StringUtils.endsWith(imageName, IMAGE_FILE_EXTENSION.GIF.getValue())) {
contentType = IMAGE_MIME_TYPE.GIF.getValue();
} else if (StringUtils.endsWith(imageName, IMAGE_FILE_EXTENSION.PNG.getValue())) {
contentType = IMAGE_MIME_TYPE.PNG.getValue();
}
response.header("Content-Type", contentType);
return response.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
*/
@PUT
@Consumes({ MediaType.MULTIPART_FORM_DATA })
@Produces({ MediaType.APPLICATION_JSON })
public String updateClientImage(@PathParam("clientId") final Long clientId, @HeaderParam("Content-Length") final Long fileSize,
@FormDataParam("file") final InputStream inputStream, @FormDataParam("file") final FormDataContentDisposition fileDetails,
@FormDataParam("file") final FormDataBodyPart bodyPart) {
return addNewClientImage(clientId, fileSize, inputStream, fileDetails, bodyPart);
}
/**
* 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)
*/
@PUT
@Consumes({ MediaType.TEXT_PLAIN, MediaType.TEXT_HTML, MediaType.APPLICATION_JSON })
@Produces({ MediaType.APPLICATION_JSON })
public String updateClientImage(@PathParam("clientId") final Long clientId, final String jsonRequestBody) {
return addNewClientImage(clientId, jsonRequestBody);
}
@DELETE
@Consumes({ MediaType.APPLICATION_JSON })
@Produces({ MediaType.APPLICATION_JSON })
public String deleteClientImage(@PathParam("clientId") final Long clientId) {
this.clientWritePlatformService.deleteClientImage(clientId);
return this.toApiJsonSerializer.serialize(new CommandProcessingResult(clientId));
}
/**
* 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)
*/
@PUT
@Consumes({ MediaType.APPLICATION_JSON })
@Produces({ MediaType.APPLICATION_JSON })
public String updateClientImage(@PathParam("clientId") final Long clientId,
final String jsonRequestBody) {
return addNewClientImage(clientId, jsonRequestBody);
}
}
@@ -5,9 +5,9 @@
*/
package org.mifosplatform.portfolio.client.exception;
import org.mifosplatform.infrastructure.core.exception.AbstractPlatformDomainRuleException;
import org.mifosplatform.infrastructure.core.exception.AbstractPlatformResourceNotFoundException;
public class ImageNotFoundException extends AbstractPlatformDomainRuleException {
public class ImageNotFoundException extends AbstractPlatformResourceNotFoundException {
public ImageNotFoundException(final String resource, final Long resourceId) {
super("error.msg.entity.image.invalid", "Image for resource " + resource + " with Identifier " + resourceId + " does not exist",
@@ -23,21 +23,21 @@ public class DefaultLoanLifecycleStateMachine implements LoanLifecycleStateMachi
switch (loanEvent) {
case LOAN_CREATED:
if (from == null) {
newState = stateOf(LoanStatus.SUBMITED_AND_PENDING_APPROVAL, allowedLoanStatuses);
newState = stateOf(LoanStatus.SUBMITTED_AND_PENDING_APPROVAL, allowedLoanStatuses);
}
break;
case LOAN_REJECTED:
if (from.hasStateOf(LoanStatus.SUBMITED_AND_PENDING_APPROVAL)) {
if (from.hasStateOf(LoanStatus.SUBMITTED_AND_PENDING_APPROVAL)) {
newState = stateOf(LoanStatus.REJECTED, allowedLoanStatuses);
}
break;
case LOAN_APPROVED:
if (from.hasStateOf(LoanStatus.SUBMITED_AND_PENDING_APPROVAL)) {
if (from.hasStateOf(LoanStatus.SUBMITTED_AND_PENDING_APPROVAL)) {
newState = stateOf(LoanStatus.APPROVED, allowedLoanStatuses);
}
break;
case LOAN_WITHDRAWN:
if (this.anyOfAllowedWhenComingFrom(from, LoanStatus.SUBMITED_AND_PENDING_APPROVAL, LoanStatus.APPROVED)) {
if (this.anyOfAllowedWhenComingFrom(from, LoanStatus.SUBMITTED_AND_PENDING_APPROVAL, LoanStatus.APPROVED)) {
newState = stateOf(LoanStatus.WITHDRAWN_BY_CLIENT, allowedLoanStatuses);
}
break;
@@ -48,7 +48,7 @@ public class DefaultLoanLifecycleStateMachine implements LoanLifecycleStateMachi
break;
case LOAN_APPROVAL_UNDO:
if (from.hasStateOf(LoanStatus.APPROVED)) {
newState = stateOf(LoanStatus.SUBMITED_AND_PENDING_APPROVAL, allowedLoanStatuses);
newState = stateOf(LoanStatus.SUBMITTED_AND_PENDING_APPROVAL, allowedLoanStatuses);
}
break;
case LOAN_DISBURSAL_UNDO:
@@ -11,7 +11,7 @@ package org.mifosplatform.portfolio.loanaccount.domain;
public enum LoanStatus {
INVALID(0, "loanStatusType.invalid"), //
SUBMITED_AND_PENDING_APPROVAL(100, "loanStatusType.submitted.and.pending.approval"), //
SUBMITTED_AND_PENDING_APPROVAL(100, "loanStatusType.submitted.and.pending.approval"), //
APPROVED(200, "loanStatusType.approved"), //
ACTIVE(300, "loanStatusType.active"), //
WITHDRAWN_BY_CLIENT(400, "loanStatusType.withdrawn.by.client"), //
@@ -29,7 +29,7 @@ public enum LoanStatus {
LoanStatus enumeration = LoanStatus.INVALID;
switch (statusValue) {
case 100:
enumeration = LoanStatus.SUBMITED_AND_PENDING_APPROVAL;
enumeration = LoanStatus.SUBMITTED_AND_PENDING_APPROVAL;
break;
case 200:
enumeration = LoanStatus.APPROVED;
@@ -77,7 +77,7 @@ public enum LoanStatus {
}
public boolean isSubmittedAndPendingApproval() {
return this.value.equals(LoanStatus.SUBMITED_AND_PENDING_APPROVAL.getValue());
return this.value.equals(LoanStatus.SUBMITTED_AND_PENDING_APPROVAL.getValue());
}
public boolean isApproved() {
@@ -26,6 +26,8 @@ import org.mifosplatform.portfolio.charge.domain.Charge;
import org.mifosplatform.portfolio.charge.domain.ChargeRepositoryWrapper;
import org.mifosplatform.portfolio.charge.exception.LoanChargeCannotBeDeletedException;
import org.mifosplatform.portfolio.charge.exception.LoanChargeCannotBeDeletedException.LOAN_CHARGE_CANNOT_BE_DELETED_REASON;
import org.mifosplatform.portfolio.charge.exception.LoanChargeCannotBeUpdatedException;
import org.mifosplatform.portfolio.charge.exception.LoanChargeCannotBeUpdatedException.LOAN_CHARGE_CANNOT_BE_UPDATED_REASON;
import org.mifosplatform.portfolio.charge.exception.LoanChargeCannotBeWaivedException;
import org.mifosplatform.portfolio.charge.exception.LoanChargeCannotBeWaivedException.LOAN_CHARGE_CANNOT_BE_WAIVED_REASON;
import org.mifosplatform.portfolio.charge.exception.LoanChargeNotFoundException;
@@ -529,6 +531,11 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
final Loan loan = retrieveLoanBy(loanId);
final LoanCharge loanCharge = retrieveLoanChargeBy(loanId, loanChargeId);
// Charges may be edited only when the loan associated with them are
// yet to be approved (are in submitted and pending status)
if (!loan.status().isSubmittedAndPendingApproval()) { throw new LoanChargeCannotBeUpdatedException(
LOAN_CHARGE_CANNOT_BE_UPDATED_REASON.LOAN_NOT_IN_SUBMITTED_AND_PENDING_APPROVAL_STAGE, loanCharge.getId()); }
final Map<String, Object> changes = loan.updateLoanCharge(loanCharge, command);
this.loanRepository.save(loan);
@@ -597,11 +604,10 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
final Loan loan = retrieveLoanBy(loanId);
final LoanCharge loanCharge = retrieveLoanChargeBy(loanId, loanChargeId);
// validate loan charge is not partly paid or waived
if (loanCharge.isWaived()) {
throw new LoanChargeCannotBeDeletedException(LOAN_CHARGE_CANNOT_BE_DELETED_REASON.ALREADY_WAIVED, loanCharge.getId());
} else if (loanCharge.isPaidOrPartiallyPaid(loan.getCurrency())) { throw new LoanChargeCannotBeDeletedException(
LOAN_CHARGE_CANNOT_BE_DELETED_REASON.ALREADY_PAID, loanCharge.getId()); }
// Charges may be deleted only when the loan associated with them are
// yet to be approved (are in submitted and pending status)
if (!loan.status().isSubmittedAndPendingApproval()) { throw new LoanChargeCannotBeDeletedException(
LOAN_CHARGE_CANNOT_BE_DELETED_REASON.LOAN_NOT_IN_SUBMITTED_AND_PENDING_APPROVAL_STAGE, loanCharge.getId()); }
loan.removeLoanCharge(loanCharge);
this.loanRepository.save(loan);
@@ -263,9 +263,9 @@ public class LoanEnumerations {
case INVALID:
optionData = new LoanStatusEnumData(LoanStatus.INVALID.getValue().longValue(), LoanStatus.INVALID.getCode(), "Invalid");
break;
case SUBMITED_AND_PENDING_APPROVAL:
optionData = new LoanStatusEnumData(LoanStatus.SUBMITED_AND_PENDING_APPROVAL.getValue().longValue(),
LoanStatus.SUBMITED_AND_PENDING_APPROVAL.getCode(), "Submitted and pending approval");
case SUBMITTED_AND_PENDING_APPROVAL:
optionData = new LoanStatusEnumData(LoanStatus.SUBMITTED_AND_PENDING_APPROVAL.getValue().longValue(),
LoanStatus.SUBMITTED_AND_PENDING_APPROVAL.getCode(), "Submitted and pending approval");
break;
case APPROVED:
optionData = new LoanStatusEnumData(LoanStatus.APPROVED.getValue().longValue(), LoanStatus.APPROVED.getCode(), "Approved");