Skip to Content

1. Introduction

This blog is about the binary content upload and retrieval in SAP Cloud Platform(SCP) Cloud Foundry(CF) through Apache Olingo OData V2, which instead of storing binary(can be image, doc etc.) in UI application as folder structure or by any other mechanism, In this way we can store the images in the form of binary object in database along with the other information. Which we can retrieve while fetching the other information. Likewise we have requirement that we have products thumbnail needs to upload in real time along with the other product information and also customer can upload new thumbnail images, So instead of storing in the UI folder or in server side, we can upload the same in the database as blob or clob object and use it whenever needs to retrieve along with the other product details.

This blog is divided into two section, the 1st part deals with the UI development, how image or any binary content can be processed in the UI using the filUpload UI5 components. The 2nd part deals with the Apache Olingo OData V2 implementation for processing upload and download of binary contents and database designs.

2. Prerequisite Activities

The prerequisites in two part, the 1st part would be the SAP UI5 app and in the 2nd part backend services along with the database can be covered.

PART-1: UI5 APP

  • Create SAPUI5 app:

The UI5 app explained here is being used to upload and fetch the image and to explain through UI, how images feature can be used in UI5 by using the FileUploader control.

In any UI5 application includes 5 files namely

  • index.html
  • Component.js – route to given JS view
  • Controller.controller.js – server call for data manipulation
  • View.view.xml – View logic and SAP UI5 controls
  • xs-app.json – application routing from UI to backend services

As mentioned above the code will be shown below along with the web project snapshots

<!DOCTYPE HTML>
<html>
	<head>
		<meta http-equiv="X-UA-Compatible" content="IE=edge">
		<meta charset="utf-8">
		<title>Image Upload & retrieval Solution</title>
		<script id="sap-ui-bootstrap"
			src="https://sapui5.hana.ondemand.com/resources/sap-ui-core.js"
			data-sap-ui-libs="sap.m"
			data-sap-ui-theme="sap_belize"
			data-sap-ui-xx-bindingSyntax="complex"
			data-sap-ui-preload="async"
			data-sap-ui-compatVersion="edge" 
			data-sap-ui-resourceroots='{"sap.ui.unified.sample.FileUploaderComplex": "./",  "sap.ui.demo.mock": "mockdata"}'>
		</script>
		<!-- Application launch configuration -->
		<script>

			sap.ui.getCore().attachInit(function() {
				new sap.m.App ({
					pages: [
						new sap.m.Page({
							title: "Image Upload & Retrieval through - Apache Olingo OData 2", 
							enableScrolling : true,
							content: [ new sap.ui.core.ComponentContainer({
								name : "sap.ui.unified.sample.FileUploaderComplex"
							})]
						})
					]
				}).placeAt("content");
			});

		</script>
	</head>
	<!-- UI Content -->
	<body class="sapUiBody" id="content" role="application">
	</body>

</html>

index.html

sap.ui.define(['sap/ui/core/UIComponent'],
	function(UIComponent) {
	"use strict";

	var Component = UIComponent.extend("sap.ui.unified.sample.FileUploaderComplex.Component", {

		metadata : {
			rootView : "sap.ui.unified.sample.FileUploaderComplex.View",
			dependencies : {
				libs : [
					"sap.ui.unified"
				]
			},
			includes : [
				"../style.css"
			],
			config : {
				sample : {
					files : [
						"View.view.xml",
						"Controller.controller.js"
					]
				}
			}
		}
	});
	return Component;
});

component.js

sap.ui.define(['jquery.sap.global', 'sap/m/MessageToast', 'sap/ui/core/mvc/Controller'],
    function(jQuery, MessageToast, Controller) {
        "use strict";

        var ControllerController = Controller.extend("sap.ui.unified.sample.FileUploaderComplex.Controller", {
            imageId: 0,
            fileData: null,
            handleUploadComplete: function(oEvent) {
                var sResponse = oEvent.mParameters;
                if (sResponse) {

                    if (sResponse.status == 200) {
                        MessageToast.show("Done");
                    } else {
                        MessageToast.show("Failed");
                    }


                }
                sap.ui.core.BusyIndicator.hide();
            },

            handleUploadPress: function(oEvent) {
                var ref = this;
					ref.imageId = Math.floor(Math.random() * 1000);
					

                $.ajax({
                    url: "/",
                    type: "GET",
                    headers: {
                        "X-Requested-With": "XMLHttpRequest",
                        "Content-Type": "application/json",
                        "X-CSRF-Token": "Fetch"
                    },
                    dataType: "json",
                    async: false,
                    complete: function(data) {
                        $.ajax({
                            "url": "/sample.svc/v1/ImageStorages",
                            "data": JSON.stringify({
                                "ImageId": ref.imageId+"",
                                "ImageMimeType": "image/jpeg",
                                "ImageBinary": btoa(ref.fileData)
                            }),
                            "processData": false,
                            "headers": {
                                "X-Csrf-Token": data.getResponseHeader('X-Csrf-Token'),
                                "Content-Type": "application/json"
                            },
                            "method": "post"
                        })

                    }
                })


            },

            handleTypeMissmatch: function(oEvent) {
                var aFileTypes = oEvent.getSource().getFileType();
                jQuery.each(aFileTypes, function(key, value) { aFileTypes[key] = "*." + value });
                var sSupportedFileTypes = aFileTypes.join(", ");
                MessageToast.show("The file type *." + oEvent.getParameter("fileType") +
                    " is not supported. Choose one of the following types: " +
                    sSupportedFileTypes);
            },

            handleValueChange: function(oEvent) {
                var file = oEvent.getParameters().files[0];
                var reader = new FileReader();
                var ref = this;
                reader.onload = function(readerEvt) {
                    ref.fileData = readerEvt.target.result;
                    //console.log(btoa(binaryString));
                };

                reader.readAsBinaryString(file)
                MessageToast.show("Press 'Upload File' to upload file '" +
                    oEvent.getParameter("newValue") + "'");
            },
            handleLoadPress: function(oEvent) {


                  var img = this.byId("img");
				  var imgId = this.imageId+"";
					$.get("/sample.svc/v1/ImageStorages('"+imgId+"')?$format=json",function(data){
					img.setSrc("data:image/jpg;base64,"+data.d.ImageBinary)
				  })


            }
        });

        return ControllerController;

    });

Controller.control.js

As you can see above in the handle press event, needs to set the image src type to jpg. So that the content can be uploaded with the defined format along with imageId which is primary key in my case. In the upload file method, we need to call the method to fetch the xsrf token. Which will be passed along with the OData API call on attach press event.

<mvc:View controllerName="sap.ui.unified.sample.FileUploaderComplex.Controller"
	xmlns:l="sap.ui.layout" xmlns:u="sap.ui.unified" xmlns:mvc="sap.ui.core.mvc"
	xmlns="sap.m" class="viewPadding">
	<l:VerticalLayout>
		<u:FileUploader id="fileUploader" name="file"
			uploadUrl="/rest/api/v1/internal/uploadImage" width="400px"
			tooltip="Upload your file to the local server" uploadComplete="handleUploadComplete"
			change="handleValueChange" style="Emphasized" fileType="jpg"
			placeholder="Choose a file for Upload...">
		</u:FileUploader>
		<Button text="Upload File" press="handleUploadPress" />

		<Button text="Load File" press="handleLoadPress" />
		<Image id="img" src="/" densityAware="false" width="500px" height="500px" ></Image>
 	</l:VerticalLayout>
</mvc:View>

View.view.xml

Here the Fileuploader ui component is being used.

{
  "welcomeFile": "index.html",
  "logout":  {
    "logoutEndpoint":  "/do/logout"
  },
  "routes": [
{
    "source": "/sample.svc/v1/(.*)",
    "destination": "java"
  },
  {
    "source": "^/(.*)",
    "localDir": "resources"
  }
]
}

xs-app.json

In the above code, if you have noticed that we have routing the URL for JAVA application. which is being routes to given Java application. The above file can be part of one web folder basically in the resource folder and the xs-app.json file be part of the main project which will be used to route the request of server for uploading and retrieving of the images or thumbnails images. You can check the details project structure and files being used in the application in the github account. The apps can be deployed in SAP CP platform as the mentioned app is being developed and evaluated in SAP CP account.

PART-2: Java Services & Database

3. Create and build JPA Project: You can refer my previous log series regarding project setup  blog series.

     3.1 Get dependent Library: To get required library refer the blog series

 

4. Configuration and Implementation

  4.1 Configure persistence.xml in JPA model: Refer to my previous blog series for this section.

  4.1.1 Configure resource.xml in JPA model: Refer to my previous blog series for this section

5. Create Required Java Classes

You need to implements two method of OnJPAWriteContent class.  But before that to enable write on such properties using OData JPA Processor Library, an additional access modifier is required to be added to the JPA entities. Following is the proposal on how OData JPA Processor Library handles java.sql.Blob and java.sql.Clob during metadata generation and runtime processing.

EDM Generation

Based on the JPA entity property type, the pseudocode for generating the EDM is as below.

For java.sql.Blob:

  1. Check if JPA entity property is of type byte[] or of type java.sql.Blob and annotated with @Lob annotation.
  2. If Step 1 is true, then generate an EDM property with type as Edm.Binary
public class OnDBWriteContent implements OnJPAWriteContent {
	final static Logger LOGGER = LoggerFactory.getLogger(OnDBWriteContent.class);
	@Override
	public Blob getJPABlob(byte[] binaryData) throws ODataJPARuntimeException {
		 try {
			 
			 LOGGER.error("IN getJPABlob");
	          return new JDBCBlob(binaryData);
	        } catch (SerialException e) {
	          ODataJPARuntimeException.throwException(ODataJPARuntimeException.INNER_EXCEPTION, e);
	        } catch (SQLException e) {
	          ODataJPARuntimeException.throwException(ODataJPARuntimeException.INNER_EXCEPTION, e);
	        }
	        return null;	
	        }

	@Override
	public Clob getJPAClob(char[] characterData) throws ODataJPARuntimeException {
		try {
			 LOGGER.error("IN getJPAClob");
	          return new JDBCClob(new String(characterData));
	        } catch (SQLException e) {
	          ODataJPARuntimeException.throwException(ODataJPARuntimeException.INNER_EXCEPTION, e);
	        }
	          return null;
	        }	

}

As you can see above in the screen shots that you need to implement the jpaBlob, jpaClob method.

5.1 Entity class added for the JPA implementations

@Entity
@Table(name="\"com.sap.sample::IMAGE_MANAGEMENT.IMAGE_STORE\"")
public class ImageStorage implements Serializable{

	private static final long serialVersionUID = 4829386985899147977L;

	@Id
	@Column(name="IMAGE_ID")
	private String imageId = null;

	@Lob
	@Column(name = "IMAGE_BINARY", length=100000000)
	private byte[] imageBinary = null;
	
	@Column(name = "IMAGE_MIME_TYPE")
	private String imageMimeType = null;

	public String getImageId() {
		return imageId;
	}

	public void setImageId(String imageId) {
		this.imageId = imageId;
	}

	public String getImageMimeType() {
		return imageMimeType;
	}

	public void setImageMimeType(String imageMimeType) {
		this.imageMimeType = imageMimeType;
	}

	public static long getSerialversionuid() {
		return serialVersionUID;
	}

	public byte[] getImageBinary() {
		return imageBinary;
	}

	public void setImageBinary(byte[] imageBinary) {
		this.imageBinary = imageBinary;
	}

In the above snapshots, you could see that data type byte [], defining the length (default length is set to 256), While loading the content. It was throwing the error of length, so to fix the issue needs to set the length for retrieving or uploading the binary content into the Database via ImageStorage Entity. I have taken an example of image content. This implementation will deal with any type of binary content, It can be doc, pdf etc. The Entity class needs to be added into the persistence.xml file. The annotation @Lob has to added for the column which mapped to byte [] columns as it bind with the binary content.

 5.2 Class override in the main JPA Service factory class

The above class needs to be set it to the writeJPAContent method of SampleCustomOdataJpaServiceFactory (refer section 3.3.2) class as shown below

setOnWriteJPAContent(onDBWriteContent);

6. Database design

As shown above the IMAGE_BINARY fields stores the image object in the blob format. The above database field might change based on the requirement. In my case I have tried to show only for the image content save as blob object, it can be added for any no. of fields and any type of content that can be stored as binary content.

7. Conclusion

This blog is about implementation of binary content and retrieval through Apache Olingo OData from database, Of course there is a documentation about the implementation but there’s no such working example and details blog or technical examples mentioned anywhere. With the above approach, we can implement how to store any image content in the form of binary or blob object into database through Olingo OData V2 implementations.


 

Few of my other blog post about the Logging in cloud foundry using jolokia apis and CDS extension features in HANA

Logging in HCP Cloud foundry with Java and Tomee using slf4j, logback, Jolokia

CDS extension feature in HANA

 

To report this post you need to login first.

2 Comments

You must be Logged on to comment or reply to a post.

Leave a Reply