Pages

Saturday, 31 May 2014

Send Multiple Files over a single Socket Connnection

This post will explain the a sample implementation to send a folder over a Socket and read the same on the Client. The first challenge is parse all the files on the Server side of the program and write the file to the output stream.
This poses a challenge as the client needs to read the bytes received and also be able to identify each file and folder so that the same can be written back. To overcome the challenge, I decided to explore the use of ZipOutputStream, where I could add each file into this output stream which writes directory to the Socket instead of the file. On the client side, I wrote the program to read the stream and replicate the folders and files.

Refer this blog for a detailed example with source code for an implementation using ZipOutputStream to write files into the network and read the the file on the client using ZipInputStream.

Code on the Server Side:

package com.gt.stick2code.filestream;

import java.io.BufferedInputStream;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.zip.CRC32;
import java.util.zip.ZipEntry;
import java.util.zip.ZipOutputStream;

public class SendFileSocketServer extends Thread {
 private static ServerSocket serverSocket;
 private Socket socket;
 private static int MAX_READ_SIZE = 1024;

 public SendFileSocketServer(Socket socket) {
  this.socket = socket;
 }

 public static void main(String[] args) throws FileNotFoundException,
   IOException {
  serverSocket = new ServerSocket(50000);
  while (true) {
   SendFileSocketServer socServer = new SendFileSocketServer(
     serverSocket.accept());
   socServer.start();
  }

 }

 public void run() {
  try {
   System.out.println("Connected");

   BufferedInputStream bis = new BufferedInputStream(
     socket.getInputStream());
   byte[] bytesToRead = new byte[MAX_READ_SIZE];
   String copyFolder = "";
   System.out.println("Reading");
   int readLength = 0;
   while (0 != (readLength = bis.read(bytesToRead))) {
    ByteArrayOutputStream bos = new ByteArrayOutputStream();
    bos.write(bytesToRead, 0, readLength);
    System.out.println("Reading::"+new String(bos.toByteArray()));
    copyFolder += new String(bos.toByteArray());
    if(readLength < MAX_READ_SIZE){
     break;
    }
   }

   File readFile = new File(copyFolder);
   if (readFile.exists()) {
    System.out.println("Reading Folder::" + copyFolder);
    ZipOutputStream zipOpStream = new ZipOutputStream(
      socket.getOutputStream());
    sendFileOutput(zipOpStream, readFile);
    zipOpStream.flush();
    System.out.println("zipOpStream Flush");

   } else {
    System.out.println("Folder to read does not exist::["+readFile.getAbsolutePath()+"]");
   }
   socket.close();
  } catch (Exception e) {
   e.printStackTrace();
  }
 }

 public void sendFileOutput(ZipOutputStream zipOpStream, File outFile)
   throws Exception {
  String relativePath = outFile.getAbsoluteFile().getParentFile()
    .getAbsolutePath();
  System.out.println("relativePath[" + relativePath + "]");
  outFile = outFile.getAbsoluteFile();
  if (outFile.isDirectory()) {
   sendFolder(zipOpStream, outFile, relativePath);

  } else {
   sendFolder(zipOpStream, outFile, relativePath);
  }
 }

 public void sendFolder(ZipOutputStream zipOpStream, File folder,
   String relativePath) throws Exception {
  File[] filesList = folder.listFiles();
  for (File file : filesList) {
   if (file.isDirectory()) {
    sendFolder(zipOpStream, file, relativePath);
   } else {
    sendFile(zipOpStream, file, relativePath);
   }
  }
 }

 public void sendFile(ZipOutputStream zipOpStream, File file,
   String relativePath) throws Exception {
  String absolutePath = file.getAbsolutePath();
  String zipEntryFileName = absolutePath;
  int index = absolutePath.indexOf(relativePath);
  if(absolutePath.startsWith(relativePath)){
   zipEntryFileName = absolutePath.substring(relativePath.length());
   if(zipEntryFileName.startsWith(File.separator)){
    zipEntryFileName = zipEntryFileName.substring(1);
   }
   System.out.println("zipEntryFileName:::"+relativePath.length()+"::"+zipEntryFileName);
  }else{
   throw new Exception("Invalid Absolute Path");
  }
  
  BufferedInputStream bis = new BufferedInputStream(new FileInputStream(
    file));
  byte[] fileByte = new byte[MAX_READ_SIZE];
  int readBytes = 0;
  CRC32 crc = new CRC32();
  while (0 != (readBytes = bis.read(fileByte))) {
   if(-1 == readBytes){
    break;
   }
   //System.out.println("length::"+readBytes);
   crc.update(fileByte, 0, readBytes);
  }
  bis.close();
  ZipEntry zipEntry = new ZipEntry(zipEntryFileName);
  zipEntry.setMethod(ZipEntry.STORED);
  zipEntry.setCompressedSize(file.length());
  zipEntry.setSize(file.length());
  zipEntry.setCrc(crc.getValue());
  zipOpStream.putNextEntry(zipEntry);
  bis = new BufferedInputStream(new FileInputStream(file));
  //System.out.println("zipEntryFileName::"+zipEntryFileName);
  while (0 != (readBytes = bis.read(fileByte))) {
   if(-1 == readBytes){
    break;
   }
   
   zipOpStream.write(fileByte, 0, readBytes);
  }
  bis.close();
  
 }

}


Code on Client Side to Read the File:


package com.gt.stick2code.filestream;

import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.Socket;
import java.util.zip.ZipEntry;
import java.util.zip.ZipInputStream;

public class SendFileSocketClient {

 public static void main(String[] args) throws FileNotFoundException,
   IOException {
  
  String outDir = "D:\\temp\\output";
  
  String readDir = ".\\amazon";
  System.out.print("Connectiong");
  Socket socket = new Socket("127.0.0.1", 50000);
  BufferedOutputStream bos = new BufferedOutputStream(socket.getOutputStream());
  System.out.print("Read::"+readDir);
  bos.write(readDir.getBytes());
  bos.flush();
  BufferedInputStream bis = new BufferedInputStream(socket.getInputStream());
  readZip(bis,outDir);
  //socket.close();
  
 }
 
 public static void readZip(InputStream socketIs,String outPutDirectory) throws IOException{
  ZipInputStream zips = new ZipInputStream(socketIs);
  ZipEntry zipEntry = null;
  
  
  while(null != (zipEntry = zips.getNextEntry())){
   String fileName = zipEntry.getName();
   File outFile = new File(outPutDirectory + "/" + fileName);
   System.out.println("----["+outFile.getName()+"], filesize["+zipEntry.getCompressedSize()+"]");
   
   
   if(zipEntry.isDirectory()){
    File zipEntryFolder = new File(zipEntry.getName());
    if(zipEntryFolder.exists() == false){
     outFile.mkdirs();
    }
    
    continue;
   }else{
    File parentFolder = outFile.getParentFile();
    if(parentFolder.exists() == false){
     parentFolder.mkdirs();
    }
   }
   
   System.out.println("ZipEntry::"+zipEntry.getCompressedSize());
   
   
   
   FileOutputStream fos = new FileOutputStream(outFile);
   int fileLength = (int)zipEntry.getSize();
   
   byte[] fileByte = new byte[fileLength];
   zips.read(fileByte);
   fos.write(fileByte);
   fos.close();
  }
 }

}



Sunday, 25 May 2014

Mapping a Webservice Data (Amazon Product Advertising API) to Database Using Spring and Hibernate


I recently had to device a program to call a webservice, read the response and then map the webservice response to a database using spring and hibernate. For this test I used the Amazon product advertisement API which provides a list of published books as a webservice.

Amazon WebService:
The first step was getting the wsdl which is available here. Once I had the wsdl, the next step was understanding the invocation steps. The API requires signing of the request for which I used a sample program available on the Amazon site itself. 
The sample program contained a SignedRequestsHelper which can be used as is, with just a few changes. The complete source can be taken from here for reference. A Git for the same is also available.

To proceed you will need the access key from the amazon affiliates program which can be obtained by creating an account, and will have to be added to the code invoking the webservice. In the sample code it has been added to AmazonFetchService.
Parameterize or replace the following lines of code:

private static final String AWS_ACCESS_KEY_ID = "<Put your Access Key Id>";
private static final String AWS_SECRET_KEY = "<Put ur Access key>";

Step 1: Fetching the WSDL
Get the wsdl for the Amazon Product advertisement API from this link.

Step 2: Generate the JAXB Classes



Step 3: Configure the Rest Template

<oxm:jaxb2-marshaller id="amazonMarshall"
  contextPath="com.test.amazon.core.xsd" />
<beans:bean id="amazonRestTemplate" class="org.springframework.web.client.RestTemplate"></beans:bean>

The file is also shared here.

Step 4: Configure the Persistance Context

I have used an h2 database and the following files can be referred for configuration:
h2config.xml -- Contains the connection details
spring-persistance-context.xml -- Contains the spring persistance context
orm.xml -- Contains the persistance unit details (needed for audit), code will work without this.
persistance.xml --

Step 5: Write the Program to Call the Service and Update the database

A Simple autowiring of the rest template and a call to its getForObject fetches the response to the webservice.
Now all that is left is to map the response object (ItemSearchResponse) to the entity object (Books_Adt). Use the magic of spring and hibernate to do the rest using a JPA repository (AmazonItemRepository.java) for persistence.

Thursday, 22 May 2014

Connecting to Twitter using HTTP Analyzer and OAuth Credentials

About:
This document shows you how you can connect to twitter to fetch user timeline data via HTTP Analyzer.

Background:
In this sample, we will be accessing the user timelines API of Twitter to get a collection of the most recent tweets posted by a user. To communicate with Twitter, an application needs to pass through the OAuth authentication scheme. HTTP Analyzer supports OAuth 1.0 at the time of writing this post.

Pre-requisites:
Use JDeveloper 12.1.2.0.0

Steps:
1.      Open HTTP Analyzer by clicking on Tools -> HTTP Analyzer and select ‘Create New HTTP Request’.
2.      Create a new credential by clicking the New… button near the credential drop down. In the resulting Preferences window, add a new credential and call it ‘OAuthCredential’.


3.      Get your Consumer key (API Key) and consumer secret (API Secret) from Twitter. If you don’t have an app already registered, do so by visiting https://dev.twitter.com/apps/new.

4.      Feed the consumer key and consumer secret into the credential window opened in step 2. 
Signature Method: HMAC_SHA1
Request token URL: https://api.twitter.com/oauth/request_token
Authorization URL: https://api.twitter.com/oauth/authorize
Access token URL: https://api.twitter.com/oauth/access_token

            The credential set-up is now done. Close the Preferences window and focus on the HTTP Analyzer.
5.      Supply the URL as https://api.twitter.com/1.1/statuses/user_timeline.json. This is the twitter URL we will be hitting to get the timeline details for a user with a given screen name. Select the Method as GET. Also, select the credential OAuthCredential created by you. The screen name and count parameters can be supplied in the Request body as shown. For details on the user timeline API, refer https://dev.twitter.com/docs/api/1.1/get/statuses/user_timeline

6.       Ignore any warning that you get by clicking Yes. You will also be prompted to accept the Certificate as this is an HTTPS request. Go ahead and do so by clicking Accept. You will then be prompted to enter your Twitter credentials for authorization. Go ahead and enter your Twitter username and password, and click Authorize app.


      Once you authorize the app, you will see the below screen. Toggle back to HTTP Analyzer to see if you have got 200 Ok as response. Once you get a 200 Ok, it means your request has gone through fine.

7.   To see the response JSON structure, go to the HTTP Analyzer pane, where all the requests are listed. Ideally, the last request will have the complete JSON data.


Monday, 12 May 2014

Setting application and feature level preferences in ADF Mobile

Usecase:
In this use case, I will demonstrate how application-level and feature-level preferences can be set in ADF Mobile.
For this, let us assume that we have a mobile application consisting of two features –
  • Employee feature showing employee information with his/her address details and 
  • Department Feature showing details for a department.

At the application level, I will demonstrate how the user can select font preferences and reflect the changes throughout the app.

To demonstrate feature preferences, I have focused on the Employee feature. Here, the user can select whether he would like to see address details or not for the employee records. Address details are specific to Employee feature and hence implemented as feature specific preference.This is an example I have used for demo purposes, but it should give you an idea on how to work with preferences.
When I was looking around to figure out how application preferences work, I didn't find much information. So, beware, this may not be the only/best approach, but it worked well for me :)

Steps:
First, let us see how application preferences can be set. In this sample, I let the user select a font style of his choice and then set it across the application. To set application level preferences, under Application Resources, go to Descriptors -> ADF META-INF-> adfmf-application.xml


Go to Preferences tab and click on the plus icon to insert a new preference group. Provide an Id and a Label, which is a human understandable title. Click Ok to close the dialog.


Next, we want to provide the user with a list of fonts from which he can select the font of his choice. To create this list, insert a preference List inside the preference group as shown:

Again, provide an Id for this preference list and a Label such as ‘Fonts’.


Now, add preference values as below:

Name
Value
Courier New - Italic
font-family:"Courier New";font-style:italic;
Courier New - Normal
font-family:"Courier New";font-style:normal;
Arial - Italic
font-family:"Arial";font-style:italic;
Arial - Normal
font-family:"Arial";font-style:normal;

Mark Arial – Normal as the default font.
Now, since we want the font preference is an application preference, we want the user selected choice to be reflected all through the app. For this, go to Employee page. Select panelPage component and through PI, select the arrow associated with Inline Style. Select Expression Builder.




What we are doing here is that, we are pointing to the application preference variable. So, any changes to the application preference value will be reflected here.  Do the same in Department page too.

That’s it. Our application level preferences are set. Now, if user changes the font selection, the corresponding changes will be reflected uniformly throughout the application.
Now, let us set a feature level preference. Feature level preferences are feature specific. Here, we will be setting feature level preference for the Employee Feature. Here, we will be giving the user the choice to view/not view address details for employees.
To add feature level preference, open adfmf-feature.xml. Select the feature corresponding to Employee feature – EmpFeature in my case, from the list of features and navigate to Preferences tab. Click the plus icon and add a preference group by providing Id and Label.


Let us add a Boolean preference inside this as shown.


Again, provide and Id and Label such as ‘showAddr’ and ‘Show Address? ’ Mark it as true by default. This will show the address details by default.


Based on the user selection, address details need to be shown/hidden. For this, in Employee AMX page, look for the main Address component and associate Rendered property with this feature preference variable. So, when user selects the checkbox to show address, the Boolean variable will be set to true, in turn setting rendered to true and displaying address details.


Lastly, when the user changes preferences and comes back to the feature, we want the changes to be reflected immediately. For this, let us create a class which implements LifeCyleListener class and associate it with the features.
In ViewController project, create a java class. In the Create Java class wizard, click plus icon associated with Implements and select LifeCycleListener(oracle.adfmf.feature). Clcik Ok to create the class. That’s it. There is no need to edit this class.


Now, we need to add  a reference to this class in the feature. Go to adfmf-feature.xml and select Employee feature, EmpFeature in my case. Go to General tab and select magnifier associated with Lifecycle Event Listener. Search for this newly created class.


Similarly, create another java class called ‘DeptFeatureLifeCycleListener’ which also implements LifeCycleListener and add a reference for it in Department Feature, DepFeature as above.


Now, you can deploy the app.
Snippets from my app:
After app loads, click Menu - > Preferences.


Select Fonts -> font of your choice. Here, I selected Courier New – Italic.

Click back button and you can see the changes.

If you move to DeptFeature, you will see the font changes there as well. Hope it helps!