Wednesday, December 5, 2012

Example of Spring, ActiveMQ to send Email





Hi,


I decided to write this blog because I could not find one simple example in combination to Spring, ActiveMQ and Email the message from queue.


Problem taken

What I am going to do in this example is:
  1. Create one Spring application, that has one POJO i.e. Entity, I took Mobile as my pojo with three attributes as Company, Model and Price.
  2. There will be one message producer which will post one message (as entity Mobile) to queue-one.
  3. There will be a listener to queue-one and on receiving a message it will de-queue the message.  Further after processing Listener post message to queue-two.
  4. This queue-two has another listener which de-queue the message from queue-two and process it to send a mail. 


Pictorial Presentation




Technologies Used

Below is the stack of technologies

  • Spring 3
  • ActiveMQ 5.6.0
  • Java Mail
  • Tomcat 7
  • Maven


How to execute and flow explanation
  1. You need to hit url http://host:port/app/main/amqmobile/form.html
  2. Based on given configuration this url will search for file form.jsp inside "WEB-INF/amqmobile" folder in your web application.
  3. As this is a GET request method in getForm of AMQMobileController will be executed.
  4. From getForm method AMQMsgProducer.produce method will be called that in turn creates 
    • AMQMobile object
    • Convert it into ObjectMessage
    • Send it to queue-one namely "amqMobileQueue"
  5. As soon as the message send to "amqMobileQueue", listener 1 i.e. AMQMobileListener will get active and its method onMessage will be executed.
  6. This is the place to perform biz logic. Now that message is send to AMQMsgSender.sendMessage method.  This method sends the message to queue-two i.e. amqMobileEmailQueue.
  7. Now Listener 2 i.e. AMQMobileEmailListener will act and its method onMessage sends the email.


Config File - applicationContext.xml

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:intg="http://www.springframework.org/schema/integration"
       xmlns:jms="http://www.springframework.org/schema/integration/jms"
       xmlns:mail="http://www.springframework.org/schema/integration/mail"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-3.1.xsd
http://www.springframework.org/schema/integration
http://www.springframework.org/schema/integration/spring-integration-2.1.xsd
        http://www.springframework.org/schema/integration/mail
        http://www.springframework.org/schema/integration/mail/spring-integration-mail-2.1.xsd">
<bean id="mailSender" class="org.springframework.mail.javamail.JavaMailSenderImpl"
          p:host="domain-address"
          p:port="587"
          p:username="username"
          p:password="password"
            >
        <property name="javaMailProperties">
            <props>
                <prop key="mail.smtp.auth">true</prop>
                <prop key="mail.smtp.starttls.enable">true</prop>
                <prop key="mail.smtp.auth.ntlm.domain" >DOMAIN</prop>
            </props>
        </property>
    </bean>


<bean id="reqAMQMobileQueue" class="org.apache.activemq.command.ActiveMQQueue">
        <property name="physicalName" value="amqMobileQueue" />
    </bean>

    <bean id="reqAMQMobileEmailQueue" class="org.apache.activemq.command.ActiveMQQueue">
        <property name="physicalName" value="amqMobileEmailQueue" />
    </bean>

    <bean id="activeMQConnectionFactory" class="org.apache.activemq.ActiveMQConnectionFactory">
        <property name="brokerURL" value="tcp://localhost:61616" />
    </bean>

    <bean id="amqMobileListener" class="com.examples.amqmobile.AMQMobileListener" />

    <bean id="container" class="org.springframework.jms.listener.DefaultMessageListenerContainer">
        <property name="connectionFactory" ref ="activeMQConnectionFactory" />
        <property name="destination" ref ="reqAMQMobileQueue"/>
        <property name="messageListener" ref ="amqMobileListener"/>
        <property name="concurrentConsumers" value="1" />
    </bean>

    <bean id="amqMobileEmailListener" class="com.examples.amqmobile.AMQMobileEmailListener" />
    <bean id="emailContainer" class="org.springframework.jms.listener.DefaultMessageListenerContainer">
        <property name="connectionFactory" ref ="activeMQConnectionFactory" />
        <property name="destination" ref ="reqAMQMobileEmailQueue"/>
        <property name="messageListener" ref ="amqMobileEmailListener"/>
        <property name="concurrentConsumers" value="1" />
    </bean>

    <bean id="jmsTemplate" class="org.springframework.jms.core.JmsTemplate">
        <property name="connectionFactory" ref="activeMQConnectionFactory" />
        <property name="defaultDestination" ref="reqAMQMobileQueue" />
    </bean>
</beans>



Config File - main-servlet.xml



<?xml version="1.0" encoding="UTF-8"?>



<beans xmlns="http://www.springframework.org/schema/beans"

       xmlns:context="http://www.springframework.org/schema/context"

       xmlns:p="http://www.springframework.org/schema/p"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-2.5.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context-2.5.xsd">

    <context:component-scan base-package="com" />
    <bean class="org.springframework.web.servlet.mvc.annotation.DefaultAnnotationHandlerMapping" />
    <bean class="org.springframework.web.servlet.view.InternalResourceViewResolver" p:prefix="/WEB-INF/jsp/" p:suffix=".jsp" />
</beans>



Config File - web.xml

<web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xmlns="http://java.sun.com/xml/ns/javaee" xmlns:web="http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd"
         xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd"
         id="WebApp_ID" version="2.5">

    <display-name>Spring examples</display-name>
    <description>Spring examples</description>

    <context-param>
        <param-name>contextConfigLocation</param-name>
        <param-value>
            /WEB-INF/classes/applicationContext.xml
        </param-value>
    </context-param>
    <listener>
        <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
    </listener>
    <servlet>
        <servlet-name>main</servlet-name>
        <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
    </servlet>
    <servlet-mapping>
        <servlet-name>main</servlet-name>
        <url-pattern>/main/*</url-pattern>
    </servlet-mapping>
</web-app>



JSP File - form.jsp (to be placed inside WEB-INF/amqmobile folder)
// This file does not contains any logic, it is just a hook point to controller

<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<%@ taglib prefix="form" uri="http://www.springframework.org/tags/form" %>

<html>
<head>
<title>Create AMQMobile</title>
</head>
<body>

<h1>Create AMQMobile</h1>

<p><b>You have successfully created a amqmobile.</b></p>

</body>
</html>



Java File - AMQMobile.java

package com.examples.amqmobile;

import java.io.Serializable;

/**
 * Created with IntelliJ IDEA.
 * User: sverma
 * Date: 18/10/12
 * Time: 3:06 PM
 */
public class AMQMobile implements Serializable {

    private String company;
    private String model;
    private int price;

    public String getCompany() {
        return company;
    }

    public void setCompany(String company) {
        this.company = company;
    }

    public String getModel() {
        return model;
    }

    public void setModel(String model) {
        this.model = model;
    }

    public int getPrice() {
        return price;
    }

    public void setPrice(int price) {
        this.price = price;
    }

    public String toString () {
        StringBuilder sb = new StringBuilder().append("Company: ").append(company)
                                  .append(", Model: ").append(model)
                                  .append(", Price: ").append(price);
        return sb.toString();
    }

}


Java File - AMQMsgProducer.java
package com.examples.amqmobile;

import org.apache.activemq.command.ActiveMQQueue;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jms.core.JmsTemplate;
import org.springframework.jms.core.MessageCreator;
import org.springframework.stereotype.Service;

import javax.jms.*;

/**
 * Created with IntelliJ IDEA.
 * User: sverma
 * Date: 5/12/12
 * Time: 10:59 AM
 */

@Service("amqMsgProducer")
public class AMQMsgProducer {

    @Autowired
    JmsTemplate jmsTemplate;

    public void produce() {

        jmsTemplate.send(new MessageCreator() {
            public Message createMessage(Session session) throws JMSException {

                AMQMobile amqMobile = new AMQMobile();
                amqMobile.setCompany("newCompany");
                amqMobile.setModel("iPhone5");
                amqMobile.setPrice(45000);

                ObjectMessage message = session.createObjectMessage(amqMobile);

                System.out.println("Sending on ["+jmsTemplate.getDefaultDestination()+"] object as " + amqMobile);

                return message;
            }
        });
    }
}


Java File - AMQMobileListener.java
package com.examples.amqmobile;

import org.apache.activemq.command.ActiveMQObjectMessage;
import org.springframework.beans.factory.annotation.Autowired;

import javax.jms.JMSException;
import javax.jms.Message;
import javax.jms.MessageListener;

/**
 * Created with IntelliJ IDEA.
 * User: sverma
 * Date: 4/12/12
 * Time: 5:18 PM
 */

public class AMQMobileListener implements MessageListener {

    @Autowired
    AMQMsgSender amqMsgSender;

    public void onMessage(Message message) {

        System.out.println("OnMessage: Listener working... " + message);
        System.out.println("OnMessage: message instance matched = " + (message instanceof ActiveMQObjectMessage));

        if (message instanceof ActiveMQObjectMessage)
        {
            try
            {
                System.out.println("Converting object to AMQMobile class.");
                AMQMobile amqMobile = (AMQMobile) ((ActiveMQObjectMessage) message).getObject();
                System.out.println("OnMessage: Object received in Listener as " + amqMobile);

                /* call message sender to put message onto second queue */
                amqMsgSender.sendMessage(amqMobile);

            }
            catch (Exception e)
            {
                e.printStackTrace();
            }
        }
    }
}


Java File - AMQMsgSender.java
package com.examples.amqmobile;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jms.core.JmsTemplate;
import org.springframework.stereotype.Service;

import javax.jms.JMSException;
import javax.jms.Queue;

/**
 * Created with IntelliJ IDEA.
 * User: sverma
 * Date: 4/12/12
 * Time: 5:21 PM
 */


@Service ("amqMsgSender")
public class AMQMsgSender {

    @Autowired
    private JmsTemplate jmsTemplate;

    @Autowired
    private Queue reqAMQMobileEmailQueue;

    /**
     * Sends message using JMS Template.
     *
     *
     * @param msg the msg
     * @throws JMSException the jMS exception
     */
    public void sendMessage(AMQMobile msg) throws JMSException
    {
        System.out.println("About to put message on queue. Queue[" + reqAMQMobileEmailQueue.toString() + "] Message[" + msg + "]");
        jmsTemplate.convertAndSend(reqAMQMobileEmailQueue, msg);
    }

    /**
     * Sets the jms template.
     *
     * @param template the jms template
     */
    public void setJmsTemplate(JmsTemplate template)
    {
        this.jmsTemplate = template;
    }

}


File - AMQMobileEmailListener.java
package com.examples.amqmobile;

import org.apache.activemq.command.ActiveMQObjectMessage;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.mail.MailSender;
import org.springframework.mail.SimpleMailMessage;

import javax.jms.Message;
import javax.jms.MessageListener;
import java.util.Date;

/**
 * Created with IntelliJ IDEA.
 * User: sverma
 * Date: 4/12/12
 * Time: 5:18 PM
 */

public class AMQMobileEmailListener implements MessageListener {

    @Autowired
    MailSender mailSender;

    public void onMessage(Message message) {

        System.out.println("OnMessage: Email Listener working... " + message);
        System.out.println("OnMessage: Email message instance matched = " + (message instanceof ActiveMQObjectMessage));

        if (message instanceof ActiveMQObjectMessage)
        {
            try
            {
                System.out.println("Converting object to AMQMobile class for email.");
                AMQMobile amqMobile = (AMQMobile) ((ActiveMQObjectMessage) message).getObject();
                System.out.println("OnMessage: Email Object received in Listener as " + amqMobile);

                /* call message sender to put message onto second queue */
                System.out.println("Sending mail for amqMobile " + amqMobile);

                SimpleMailMessage msg = new SimpleMailMessage();

                msg.setTo("sverma@impetus.co.in");

                msg.setFrom("sverma@impetus.co.in");
                msg.setSubject("Transforming from activemq");
                msg.setSentDate(new Date());
                msg.setText("Transforming using spring for mobile " + amqMobile);

                System.out.println("Waiting for 10 sec intentionally bf sending mail...");
                Thread.sleep(10000); // Mail server was throwing error of message sending limit in given time frame exceeds

                mailSender.send(msg);

            }
            catch (Exception e)
            {
                e.printStackTrace();
            }
        }
    }
}


Java File - AMQMobileController.java
package com.examples.amqmobile;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;

/**
 * Created with IntelliJ IDEA.
 * User: sverma
 * Date: 18/10/12
 * Time: 7:38 PM
 */

@Controller
public class AMQMobileController {

    @Autowired
    AMQMsgProducer amqMsgProducer;

    @RequestMapping (value = "/amqmobile/form.html", method = RequestMethod.GET)
    public void getForm(Model model) {
        System.out.println("AMQController: Adding amq mobile in a model");
        amqMsgProducer.produce();
        System.out.println("AMQController: Msg send");
    }
}

Although I tried to keep my logic focus, but definitely I need to work more on formatting skills :)
I am skipping other information like maven entries etc. Please do let me know if you want any help on above.

Above Code is at GitHub, click here.


- Shailendra Verma


Note: I tested this application; after commenting Thread.sleep and mail.send command to save mailbox; with jMeter for 5000 simultaneous threads and there is no exception.  All threads executed successfully.  Hurrah !!  Infact jMeter give up by outOfMemory exception :)

Tuesday, November 6, 2012

SQL Questions

Date: 06-Nov-2012

SQL: Inner / Sub query group wise problem

This blog contains questions which are different in problem solving approach and related to IT world. Origin is from day to day discussion / interview / other findings. 

Question:

Consider a schema

ID | Category | Keyword | Bid Price

Write a sql to fetch out top 5 keywords based on the bid price per category.
No RDBMS functions, only SQL.


Details/assumptions:

Table definition considered on oracle 10.x
create table test (
ID number,
Category varchar (20),
Keyword varchar (20),
BidPrice number
);

Data:

insert into test values (1, 'Category-A', 'Keyword-A1', 110);
insert into test values (2, 'Category-A', 'Keyword-A2', 121);
insert into test values (3, 'Category-A', 'Keyword-A3', 130);
insert into test values (4, 'Category-A', 'Keyword-A4', 125);
insert into test values (5, 'Category-A', 'Keyword-A5', 115);
insert into test values (6, 'Category-A', 'Keyword-A6', 133);
insert into test values (7, 'Category-B', 'Keyword-B1', 105);
insert into test values (8, 'Category-B', 'Keyword-B2', 111);
insert into test values (9, 'Category-B', 'Keyword-B3', 108);
insert into test values (10, 'Category-B', 'Keyword-B4', 128);
insert into test values (11, 'Category-B', 'Keyword-B5', 144);
insert into test values (12, 'Category-B', 'Keyword-B6', 101);
insert into test values (13, 'Category-C', 'Keyword-C1', 150);
insert into test values (14, 'Category-C', 'Keyword-C2', 137);
insert into test values (15, 'Category-C', 'Keyword-C3', 126);
insert into test values (16, 'Category-C', 'Keyword-C4', 121);
insert into test values (17, 'Category-C', 'Keyword-C5', 112);
insert into test values (18, 'Category-C', 'Keyword-C6', 118);




Output expected:

KEYWORD       CATEGORY     BIDPRICE
------------- ------------- ----------
Keyword-A3      Category-A   130
Keyword-A4      Category-A   125
Keyword-A2      Category-A   121
Keyword-A5      Category-A   115
Keyword-A1      Category-A   110
Keyword-B5      Category-B   144
Keyword-B4      Category-B   128
Keyword-B2      Category-B   111
Keyword-B3      Category-B   108
Keyword-B1      Category-B   105
Keyword-C1      Category-C   150
Keyword-C2      Category-C   137
Keyword-C3      Category-C   126
Keyword-C4      Category-C   121
Keyword-C5      Category-C   112


Answer:

Till this point all the provided ingredients are enough to cook our sql for a given scenario.  Still if you do not want to try your own and want to see result have a look:

Oracle specific solution:

SELECT KEYWORD ,CATEGORY, BIDPRICE
FROM
(
  SELECT KEYWORD ,CATEGORY, BIDPRICE,
          ROW_NUMBER() OVER (PARTITION BY CATEGORY ORDER BY BIDPRICE DESC) rn
  FROM test
) a
WHERE rn <= 5
ORDER BY CATEGORY, BIDPRICE DESC


Final Solution:
Below is the only SQL solution which is accepted as a final solution is:

SELECT 
    A.KEYWORD, A.CATEGORY, A.BIDPRICE 
FROM TEST A,  (
        SELECT 
            B.ID, B.CATEGORY 
        FROM TEST B 
        WHERE 
        (
            SELECT 
                DISTINCT(C.CATEGORY) 
            FROM TEST C 
            WHERE 
                B.CATEGORY=C.CATEGORY
        ) = B.CATEGORY 
        ORDER BY B.BIDPRICE DESC
    ) D WHERE A.ID IN 
        (SELECT ID FROM 
            (SELECT E.ID, E.CATEGORY FROM TEST E ORDER BY E.BIDPRICEDESC) F 
        WHERE A.CATEGORY=F.CATEGORY AND ROWNUM<=5
    ) 
GROUP BY A.CATEGORY, A.BIDPRICE, A.KEYWORD ORDER BY A.CATEGORY, A.BIDPRICE DESC;




Please let me know if you have any question.

Thanks
Shailendra 

Thursday, April 26, 2012

SmartClient: Insert controls between radio items

Problem:

I was going through SmartClient forum and read one problem (here is the link) stating as:

Insert a select box in between the radio options.
In SmartClient whenever I use radio group, I specify a value map, all the radio options are displayed together (one after other) in order as they are mentioned in value map. 
Is there any way by which I can insert any other component in between these options? 
I want something like, 1st radio option then a select box, 2nd option then a select box and so on...

Solution:

I tried some of the options and played around some tweaks.  What I get is, if you keep the name of radio group item same and then pass on same item to your dynamic form control with different valueMap you can achieve this.  I understand, some thing hard to imagine without code :).  So, here we go ....

But wait :) .... don't worry I will share the code, but I just want to show the result first.

Result:



Code:

<HTML>
<HEAD>
<SCRIPT>var isomorphicDir = "../content/isomorphic/";</SCRIPT>
<SCRIPT SRC="../content/isomorphic/system/modules/ISC_Core.js"></SCRIPT>
<SCRIPT SRC="../content/isomorphic/system/modules/ISC_Foundation.js"></SCRIPT>
<SCRIPT SRC="../content/isomorphic/system/modules/ISC_Containers.js"></SCRIPT>
<SCRIPT SRC="../content/isomorphic/system/modules/ISC_Grids.js"></SCRIPT>
<SCRIPT SRC="../content/isomorphic/system/modules/ISC_Forms.js"></SCRIPT>
<SCRIPT SRC="../content/isomorphic/system/modules/ISC_DataBinding.js"></SCRIPT>
<SCRIPT SRC="../content/isomorphic/system/modules/ISC_PluginBridges.js"></SCRIPT>
<SCRIPT SRC="sc_common.js"></SCRIPT>

<!-- Isomorphic Skin -->
<SCRIPT SRC="../content/isomorphic/skins/SilverWave/load_skin.js"></SCRIPT>
</HEAD>

<BODY>
<!-- $Id: cm_admin.js 875 2006-11-17 23:34:46Z sverma $ . -->
<SCRIPT LANGUAGE="JavaScript" TYPE="text/javascript">



function t (form, item, value, oldValue) {
    item.clearValue ();
    alert (item.name+"---"+value);
}

isc.DynamicForm.create ({
    autoDraw: true,
    fields: [
        {
         name: "Radio",
         title: "Title One",
         type: "radioGroup",
         change: function (form, item, value, oldValue) {t (form, item, value, oldValue);},
         valueMap: ["Yes"]
         //valueMap: {"1":"Yes"}
         },
         {
         name: "Shailendra",
         defaultValue: "Enjoy"
         },
         {
         name: "Radio",
         title: "Title Two",
         type: "radioGroup",
         change: function (form, item, value, oldValue) {t (form, item, value, oldValue);},
         valueMap: ["No"]
         //valueMap: {"2":"No"}
         }
    ]
});



</SCRIPT>

</BODY>
</HTML>


 -- Enjoy !!

Let me know if you have any questions.

-shaILU
PS: I updated this function because after giving same name of the radio items it is not refreshing once all radio items are selected once.

Friday, April 6, 2012

Thirdparty Chart engine in SmartClient

Hi All,


In SmartClient AJAX RIA Framework, default chart engine that supported is Fusion Charts (http://www.fusioncharts.com/). Although free version of Fusion Charts might works well for you but there could be requirement where you may want to use any other chart engines which are more popular, powerful and fits in your requirement.


I am demonstrating here, how one can plug-in the amcharts (http://www.amcharts.com/) in SmartClient components that I implemented during requirement of amcharts in one of my project.


Before we start, you need to download desired chart and its supporting plug-ins, fonts etc.  Put them in your web application parallel to the SmartClient (isomorphic) directory and setup the path of desired chart flash files & other supporting files.


First you need to make a Class in SmartClient that extends Canvas Class.  Canvas class is one of the base Class provided by SmartClient.  All drawing components like HLayout, VLayout etc extends this class.  You can think of Canvas as Object class in java.


Here goes the code to create AMChart class:




// ------- Code Starts ---------------



/* A shim to AMChart for charting*/
isc.defineClass("AMChart", isc.Canvas).addProperties({
    redrawOnResize: true,
    autoDraw: false,
    overflow: "auto",
    type: "line",
    min: 0,
    max: 100,
    jsonProps: "",
    graph: "",


    initWidget: function() {
        graph = null;
        this.Super("initWidget", arguments);
    },


    getCanvasID: function() {
        return this.getID() + "_canvas";
    },


    getChartDivID: function() {
        return this.getCanvasID() + "_div";
    },


    getInnerHTML: function() {
        return '<div id="'+this.getCanvasID()+'"><strong>Try '+applyLinkCSS("Refresh","refreshSCComponent (\""+this.getID()+"\")") + ' or You need to ' + applyLinkCSS("upgrade","window.open(\"http://www.adobe.com/support/flashplayer/downloads.html\")")+' your Flash Player</strong></div>';
    },


    draw: function() {
        this.Super("draw", arguments);
        this.drawGraph();
        return this;
    },


    redraw: function() {
            this.Super("redraw", arguments);
            /*this.drawGraph();*/
            return this;
    },


    setData: function(data) {


            return this;
    },


    setSettings: function(data) {


            return this;
    },


    appendData: function(data) {
            eval(this.getChartDivID()).appendData (data, "0");
/*            document.getElementById(this.getChartDivID()).appendData (data, "0");*/
    },


    drawGraph: function() {
        var self = this;


        graph = new SWFObject(getChartSWFPath(this.type), this.getChartDivID(),
                                                            String(this.getInnerWidth()), String(this.getInnerHeight()), "8", "#FFFFFF");


        /* need to see this line*/


        /*g[this.theme]();*/
        graph.title = this.title;


        graph.addVariable("path", getChartRootPath(this.type));
        graph.addVariable("settings_file", escape(getChartSettingFile (this.type)));
        /* Column
        graph.addVariable("chart_settings", encodeURIComponent("<settings><text_size>12</text_size><background><alpha>100</alpha><border_alpha>20</border_alpha></background><grid><category><dashed>1</dashed></category><value><dashed>1</dashed></value></grid><axes><category><width>1</width><color>E7E7E7</color></category><value><width>1</width><color>E7E7E7</color></value></axes><values><value><min>0</min></value></values><depth>15</depth><column><width>85</width><balloon_text>{title}: {value} downloads</balloon_text><grow_time>3</grow_time></column><graphs><graph gid='0'><title>Stock</title><color>ADD981</color></graph><graph gid='1'><title>Column</title><color>7F8DA9</color></graph><graph gid='2'><title>Line</title><color>FEC514</color></graph></graphs><labels><label lid='0'><text><![CDATA[<b>Dailyyyy downloads</b>]]></text><y>18</y><text_color>000000</text_color><text_size>13</text_size><align>center</align></label></labels></settings>"));
        graph.addVariable("chart_settings", encodeURIComponent("<settings><graphs><graph gid='0'><title>Stock</title></graph><graph gid='1'><title>Column</title></graph><graph gid='2'><title>Line</title></graph></graphs><labels><label lid='0'><text><![CDATA[<b>Dailyyyy downloads</b>]]></text><y>18</y><text_color>000000</text_color><text_size>13</text_size><align>center</align></label></labels></settings>"));*/
        /*Column*/
        graph.addVariable('chart_data', encodeURIComponent(this.data));
        graph.addVariable("chart_settings", encodeURIComponent(getChartSettings(this.type, eval(getChartJson(this.jsonProps)))));
        /*graph.addVariable("preloader_color", "#999999");*/
        /*graph.addParam("wmode", "opaque");*/


        graph.write(this.getCanvasID());
    }
});


// ------- Code ends ---------------




For time being I put some graph properties over here but you can try out to pass these properties as parameters.


Code that will call this above class and will actual generates the AMChart is below:




// ------- Code starts ---------------



isc.AMChart.create ({
        title: "Nifty",
        min: "5450",
        max: "5490",
        unit: "",
        jsonProps: "min,max,unit",
        type: CHART_TYPE_LINE,
        data : "<chart><series><value xid='0'>10 am</value><value xid='1'>11 am</value>" +
                "<value xid='2'>12 pm</value><value xid='3'>1 pm</value><value xid='4'>2 pm</value></series>" +
                "<graphs><graph gid='1'><value xid='0'>5460</value><value xid='1'>5466</value>" +
                "<value xid='2'>5465</value><value xid='3'>5450</value><value xid='4'>5470</value>" +
                "</graph></graphs></chart>"
    })


// ------- Code ends ---------------


Now you can store this component into any variable and attach to any layout. Few other supporting functions/constants that are referred in above code are as below:


// ------- Code starts ---------------

/********************************<!-- amCharts constants -->***************/
var AM_CHART_ROOT       =  context + "/thirdparty/amcharts/";

var AMCOLUMN_ROOT               = AM_CHART_ROOT+"column/";
var AMCOLUMN_SWF_OBJECT = AMCOLUMN_ROOT+"amcolumn.swf";
var AMCOLUMN_PATH               = AMCOLUMN_ROOT ;
var AMCOLUMN_SETTING         = AMCOLUMN_ROOT+"amcolumn_settings.xml";

var AMLINE_ROOT                       = AM_CHART_ROOT+"line/";
var AMLINE_SWF_OBJECT         = AMLINE_ROOT+"amline.swf";
var AMLINE_PATH                       = AMLINE_ROOT ;
var AMLINE_SETTING                 = AMLINE_ROOT+"amline_settings.xml";

var AMPIE_ROOT                         = AM_CHART_ROOT+"pie/";
var AMPIE_SWF_OBJECT           = AMPIE_ROOT+"ampie.swf";
var AMPIE_PATH                         = AMPIE_ROOT ;
var AMPIE_SETTING                   = AMPIE_ROOT+"ampie_settings.xml";

var AMRADAR_ROOT                 = AM_CHART_ROOT+"radar/";
var AMRADAR_SWF_OBJECT   = AMRADAR_ROOT+"amradar.swf";
var AMRADAR_PATH                 = AMRADAR_ROOT ;
var AMRADAR_SETTING           = AMRADAR_ROOT+"amradar_settings.xml";

var AMSTOCK_ROOT                 = AM_CHART_ROOT+"stock/";
var AMSTOCK_SWF_OBJECT   = AMSTOCK_ROOT+"amstock.swf";
var AMSTOCK_PATH                 = AMSTOCK_ROOT ;
var AMSTOCK_SETTING           = AMSTOCK_ROOT+"amstock_settings.xml";

var AMSTOCK_INTRADAY_ROOT                 = AM_CHART_ROOT+"intraday_data/";
var AMSTOCK_INTRADAY_SETTING           = AMSTOCK_INTRADAY_ROOT+"amstock_settings.xml";

var AMXY_ROOT                        = AM_CHART_ROOT+"xy/";
var AMXY_SWF_OBJECT          = AMXY_ROOT+"amxy.swf";
var AMXY_PATH                        = AMXY_ROOT;
var AMXY_SETTING                  = AMXY_ROOT+"amxy_settings.xml";
/***************<!-- /amCharts constants -->     ******/

var XY = "xy";
var PIE = "pie";
var RADAR = "radar";
var COLUMN = "column";
var LINE = "line";
var STOCK = "stock";

var CHART_TYPE_LINE = "line";
var CHART_TYPE_STOCK_INTRADAY = "stock-intraday";
var CHART_TYPE_ONE_DAY_INTRADAY = "stock-intraday";

function getChartJson (jsonStr) {
    if (hasValue (jsonStr)) {
        var arr = jsonStr.split(",");
        var json = "[{";
        for (var i=0 ; i < arr.length; i++) {
            json += arr[i] + ": this." + arr[i] + ",";
        }
        json =  json.substring(0, json.length-1);
        json += "}]";

        return json;
    }
    return "[]";
}

function getChartSettings (type, chartJson) {
    var setting = "";
    if (type == CHART_TYPE_LINE)
        setting = getLineChartSettings (chartJson);
    if (type == CHART_TYPE_STOCK_INTRADAY)
        setting = getStockIntradayChartSettings (chartJson);
    if (type == CHART_TYPE_ONE_DAY_INTRADAY)
        setting = getIntra1dayChartSettings (chartJson);

   /* alert ("setting = "+setting); */
    return setting;
}

function getChartSettingFile(type) {
    var settingsPath = (AM_CHART_ROOT+type+"/am"+type+"_settings.xml");
    if (type == CHART_TYPE_STOCK_INTRADAY || type == CHART_TYPE_ONE_DAY_INTRADAY)
        settingsPath = AM_CHART_ROOT+"stock/intraday_data/amstock_settings.xml";
/*    alert ("settings Path="+settingsPath)*/
    return settingsPath;
}

function getChartSWFPath(type) {
    var path = AM_CHART_ROOT+type+"/am"+type+".swf?cache=0";
    if (type == CHART_TYPE_STOCK_INTRADAY || type == CHART_TYPE_ONE_DAY_INTRADAY)
        path = AM_CHART_ROOT+"stock/amstock.swf?cache=0";
/*    alert ("swf path="+path)*/
    return path;
}

function getChartRootPath (type) {
    var path = AM_CHART_ROOT + type + "/";
    if (type == CHART_TYPE_STOCK_INTRADAY || type == CHART_TYPE_ONE_DAY_INTRADAY)
        path = AM_CHART_ROOT + "stock/";

/*    alert ("root path="+path)*/
    return path;
}

/* Specific Chart settings for Sensex and Nifty (single axis line chart)
@param min - minimum value of the Y-Axis to show
@param max - maximum value of the Y-Axis to show
@param unit - text to show as prefix on Y-Axis
*/
function getLineChartSettings (chartJson) {
    return "<settings>" +
            /*"<reload_data_interval>6</reload_data_interval>" +*/
            "<digits_after_decimal>2</digits_after_decimal>" +
            "<plot_area><border_alpha>100</border_alpha><border_color>F0E6B0</border_color>" +
            "<margins><top>10</top><bottom>30</bottom><left>5</left><right>3</right></margins>" +
            "</plot_area><grid><x><color>F0E6B0</color><alpha>100</alpha></x><y_left>" +
            "<color>F0E6B0</color><alpha>100</alpha></y_left></grid><axes><x><width>1</width>" +
            "<color>F0E6B0</color></x><y_left><alpha>100</alpha></y_left></axes><values><x>" +
            "<skip_first>true</skip_first><skip_last>false</skip_last></x><y_left>" +
            "<min>"+chartJson[0].min+"</min><max>"+chartJson[0].max+"</max><unit>"+chartJson[0].unit+"</unit><unit_position>left</unit_position>" +
            "<inside>true</inside><skip_last>true</skip_last></y_left></values><indicator>" +
            "<zoomable>false</zoomable><color>6A9DA6</color>" +
            "<x_balloon_text_color>FFFFFF</x_balloon_text_color><y_balloon_on_off>false</y_balloon_on_off></indicator>" +
            "<balloon><alpha>80</alpha></balloon><legend><enabled>false</enabled></legend>" +
            "<graphs><graph gid='zero'><color>#000000</color><bullet>round_outlined</bullet></graph></graphs></settings>";
}

function getStockIntradayChartSettings (chartJson) {

    return "<settings><margins>0</margins><max_series>0</max_series><redraw>true</redraw>" +
        "<max_grid_count>30</max_grid_count>" +
        "<data_sets><data_set did=\"0\">" +
        "<title>East Stock</title><short>ES</short><color>7f8da9</color>" +
        "<file_name>data/data-at-irregular-intervals.txt</file_name>" +
        "<csv>" +
        "<separator>,</separator><date_format>MM/DD/YYYY hh:mm</date_format><decimal_separator>.</decimal_separator>" +
        "<columns><column>date</column><column>open</column><column>high</column><column>low</column>" +
        "<column>close</column><column>volume</column></columns>" +
        "<data>"+chartJson[0].data+"</data>" +
        "</csv></data_set></data_sets><trend_lines><drawing_enabled>true</drawing_enabled></trend_lines><charts><trend_lines><drawing_enabled>true</drawing_enabled></trend_lines><chart cid=\"0\"><trend_lines><drawing_enabled>true</drawing_enabled></trend_lines><height>60</height><title>Value</title>" +
        "<border_color>#CCCCCC</border_color><border_alpha>0</border_alpha><values><y_left><bg_alpha>70</bg_alpha>" +
        "<bg_color>000000</bg_color><text_color>FFFFFF</text_color></y_left></values>" +
        "<legend><show_date>true</show_date><positive_color>7f8da9</positive_color>" +
        "<negative_color>db4c3c</negative_color></legend>" +
        "<column_width>70</column_width>" +
        "<graphs><graph gid=\"0\"><type>line</type>" +
        "<data_sources> <open>open</open> <high>high</high> <low>low</low> " +
        "<close>close</close></data_sources><cursor_color>002b6d</cursor_color><positive_color>7f8da9</positive_color>" +
        "<negative_color>db4c3c</negative_color><fill_alpha></fill_alpha> " +
        "<legend><date key=\"false\" title=\"false\">" +
        "<![CDATA[open:<b>{open}</b>, low:<b>{low}</b>, high:<b>{high}</b>, close:<b>{close}</b>]]>" +
        "</date><period key=\"false\" title=\"false\">" +
        "<![CDATA[open:<b>{open}</b>, low:<b>{low}</b>, high:<b>{high}</b>, close:<b>{close}</b>]]>" +
        "</period></legend></graph> </graphs></chart> <chart cid=\"1\"><trend_lines><drawing_enabled>true</drawing_enabled></trend_lines><height>30</height>" +
        "<border_color>#CCCCCC</border_color><border_alpha>0</border_alpha><title>Volume</title><values>" +
        "<y_left><bg_alpha>70</bg_alpha><bg_color>000000</bg_color><text_color>FFFFFF</text_color>" +
        "</y_left></values> <legend><show_date>true</show_date><positive_color>7f8da9</positive_color>" +
        "<negative_color>db4c3c</negative_color></legend><column_width>0</column_width>" +
        "<graphs><graph gid=\"0\"><type>column</type>" +
        "<data_sources> <close>volume</close></data_sources>" +
        "<cursor_color>002b6d</cursor_color><positive_color>7f8da9</positive_color>" +
        "<negative_color>db4c3c</negative_color><fill_alpha>100</fill_alpha> " +
        "<legend><date key=\"false\" title=\"false\">" +
        "<![CDATA[{close}]]></date>" +
        "<period key=\"false\" title=\"false\">" +
        "<![CDATA[total:{sum}]]>" +
        "</period></legend></graph> </graphs></chart> </charts>" +
        "<data_set_selector><enabled>false</enabled></data_set_selector>" +
        "<period_selector><button><bg_color_hover>FEC514</bg_color_hover>" +
        "<bg_color_selected>DB4C3C</bg_color_selected>" +
        "<text_color_selected>FFFFFF</text_color_selected></button>" +
        "<periods> <period type=\"hh\" count=\"12\">12h</period>" +
        "<period type=\"DD\" count=\"1\" selected=\"true\">1D</period>" +
        "<period type=\"DD\" count=\"3\">2D</period><period type=\"DD\" count=\"4\">3D</period>" +
        "<period type=\"DD\" count=\"5\">4D</period><period type=\"DD\" count=\"6\">5D</period>" +
        "<period type=\"MAX\">MAX</period></periods><periods_title>Zoom:</periods_title>" +
        "<custom_period_title>Custom period:</custom_period_title></period_selector>" +
        "<date_formats>" +
        "<!-- [24] (12 / 24) The time in the legend and x axis might be displayed using 12 or 24 hour format -->" +
        "<hour_format></hour_format><legend><minutes>DD month, YYYY hh:mm</minutes>" +
        "<hours>DD month, YYYY hh:mm</hours><days>DD month, YYYY hh:mm</days>" +
        "<months>DD month, YYYY hh:mm</months><years>DD month, YYYY hh:mm</years></legend></date_formats>" +
        "<header><enabled>false</enabled></header><plot_area><border_color>b6bece</border_color></plot_area>" +
        "<scroller><graph_data_source>close</graph_data_source><graph_selected_fill_alpha>100</graph_selected_fill_alpha>" +
        "<resize_button_color>002b6d</resize_button_color><playback><enabled>true</enabled>" +
        "<color>002b6d</color><color_hover>db4c3c</color_hover><speed>3</speed><max_speed>10</max_speed>" +
        "<speed_indicator><color>002b6d</color></speed_indicator></playback></scroller></settings>";

}


// ------- Code ends ---------------



I tried to explain how any other chart engine can be plugged into SmartClient.  Please fill free to ask any other question if some thing is not clear.  Either comment or mail me at shailendravermag@gmail.com


Thanks
shaILU

Monday, November 21, 2011

Download a file without Server trip - HTML + JSP

Hi,
Many times we as a developer has data on client side for the end user and on his request wants to prompt user (Save As dialog box) to download that data.
The solution to download local/client-side contents via javascript is not straight forward. I have implemented one solution using SmartClient, Html and JSP.

Being a developer and not a good philosopher 'm coming straight to the solution:

Problem:
I am in the project in which client side is implemented in SmartClient8. We need to download/export data of a grid (a table like structure). We were using RESTish web services to serve the data from Server side. So I want to ignore the server call for the same data to download that I am showing in the grid.

Solution:
What I did is made two JSPs namely: blank.jsp and export.jsp.
File blank.jsp is literally blank, absolutely no code. Now I need to export the grid data that I already have on client side in JSON format [JavaScript Object Notation - you can google it :)].
Now when ever user asks to export the grid data (by clicking a button on UI), I do below:
    a. Open a new window with url blank.jsp
    b. using document.write I create a form in it with one field name text in it and set data to export inside it.
    c. Now POST that form to export.jsp present in same hierarchy.
    d. I am pasting code of export.jsp and is self explanatory.

// -------------------------- code start -------------------------------------

<%@ page import="java.util.*,java.io.*,java.util.Enumeration"%>
<%
response.setContentType ("text/csv");
//set the header and also the Name by which user will be prompted to save
response.setHeader ("Content-Disposition", "attachment;filename=\"data.csv\"");
String contents = request.getParameter ("text");
if (!(contents!= null && contents!=""))
contents = "No data";
else
contents = contents.replaceAll ("NEW_LINE", "\n");

//Open an input stream to the file and post the file contents thru the
//servlet output stream to the client m/c

InputStream in = new ByteArrayInputStream(contents.getBytes ());
ServletOutputStream outs = response.getOutputStream();

int bit = 256;
int i = 0;
try {
while ((bit) >= 0) {
bit = in.read();
outs.write(bit);
}
//System.out.println("" +bit);
} catch (IOException ioe) {
ioe.printStackTrace(System.out);
}
outs.flush();
outs.close();
in.close();
%>



<%script type="text/javascript">
try {window.close ();} catch (e) {alert (e);}
<%/script>



// -------------------------- code end -------------------------------------

Above functionality is cross-browser and working fine on IE, FF, Safari, Chrome.
This code is tested, deployed and working in production environment.


Thanks shaILU